This is an object that is used to build an LDAP filter for Treequel::Branchsets.
== Grammar (from RFC 2254) ==
filter = “(” filtercomp “)” filtercomp = and / or / not / item and = “&” filterlist or = “|” filterlist not = “!” filter filterlist = 1filter item = simple / present / substring / extensible simple = attr filtertype value filtertype = equal / approx / greater / less equal = “=” approx = “~=” greater = “>=” less = “<=” extensible = attr [“:dn”] [“:” matchingrule] “:=” value / [“:dn”] “:” matchingrule “:=” value present = attr “=” substring = attr “=” [initial] any [final] initial = value any = “*” (value “”) final = value attr = AttributeDescription from Section 4.1.5 of [1] matchingrule = MatchingRuleId from Section 4.1.9 of [1] value = AttributeValue from Section 4.1.6 of [1]
The default filter expression to use when searching if none is specified
The mapping of leftmost symbols in a boolean expression and the corresponding FilterComponent class.
An equivalence mapping of operation names from Sequel expressions into Treequel equivalents
A list of filtertypes that come in as Sequel::Expressions; these generated nicer exception messages that just 'unknown filtertype'
The filtercomp part of the filter
Create a new Treequel::Branchset::Filter with the specified
expression
.
# File lib/treequel/filter.rb, line 674
def initialize( *expression_parts )
self.log.debug "New filter for expression: %p" % [ expression_parts ]
@component = self.class.parse_expression( expression_parts )
self.log.debug " expression parsed into component: %p" % [ @component ]
super()
end
Turn the specified expression Array into a Treequel::Filter::Component object and return it.
# File lib/treequel/filter.rb, line 461
def self::parse_array_expression( expression )
self.log.debug "Parsing Array expression %p" % [ expression ]
case
# [ ] := '(objectClass=*)'
when expression.empty?
self.log.debug " empty expression -> objectClass presence item component"
return Treequel::Filter::PresentItemComponent.new
# Collection of subfilters
# [ [:uid, 'mahlon'], [:employeeNumber, 20202] ]
when expression.all? {|elem| elem.is_a?(Array) }
self.log.debug " parsing array of subfilters"
filters = expression.collect {|exp| Treequel::Filter.new(exp) }
if filters.length > 1
return Treequel::Filter::AndComponent.new( filters )
else
return filters.first
end
# Literal filters [ 'uid~=gung', 'l=bangkok' ] := '(uid~=gung)(l=bangkok)'
when expression.all? {|item| item.is_a?(String) }
filters = expression.collect {|item| Treequel::Filter.new(item) }
return Treequel::Filter::FilterList.new( filters )
# Collection of subfilter objects
when expression.all? {|elem| elem.is_a?(Treequel::Filter) }
return Treequel::Filter::FilterList.new( expression )
# [ :attribute ] := '(attribute=*)'
when expression.length == 1
return self.parse_expression( expression[0] )
when expression[0].is_a?( Symbol )
return self.parse_tuple_array_expression( expression )
else
raise Treequel::ExpressionError,
"don't know how to turn %p into a filter component" % [ expression ]
end
end
Turn the specified filter expression
into a Treequel::Filter::Component object and
return it.
# File lib/treequel/filter.rb, line 422
def self::parse_expression( expression )
self.log.debug "Parsing expression %p" % [ expression ]
expression = expression[0] if expression.is_a?( Array ) && expression.length == 1
case expression
# String-literal filters
when String
return expression
# 'Item' components
when Array
return self.parse_array_expression( expression )
# Composite item components
when Hash
return self.parse_hash_expression( expression )
# Unwrapped presence item filter
when Symbol
return Treequel::Filter::PresentItemComponent.new( expression )
# Support Sequel expressions
when Sequel::SQL::Expression
return self.parse_sequel_expression( expression )
# Filters and components can already act as components of other filters
when Treequel::Filter, Treequel::Filter::Component
return expression
else
raise Treequel::ExpressionError,
"don't know how to turn %p into an filter component" % [ expression ]
end
end
Parse one or more tuples contained in a Hash into an ANDed set of Treequel::Filter::Components and return it.
# File lib/treequel/filter.rb, line 508
def self::parse_hash_expression( expression )
self.log.debug "Parsing Hash expression %p" % [ expression ]
filterlist = expression.collect do |key, expr|
self.log.debug " adding %p => %p to the filter list" % [ key, expr ]
if expr.respond_to?( :fetch )
if expr.respond_to?( :length ) && expr.length > 1
self.log.debug " ORing together %d subfilters since %p has indices" %
[ expr.length, expr ]
subfilters = expr.collect {|val| Treequel::Filter.new(key, val) }
Treequel::Filter.new( :or, subfilters )
else
self.log.debug " unwrapping singular subfilter"
Treequel::Filter.new([ key.to_sym, expr.first ])
end
else
self.log.debug " value is a scalar; creating a single filter"
Treequel::Filter.new( key.to_sym, expr )
end
end
if filterlist.length > 1
return Treequel::Filter::AndComponent.new( *filterlist )
else
return filterlist.first
end
end
Parse an item component from the specified attribute
and
value
# File lib/treequel/filter.rb, line 586
def self::parse_item_component( attribute, value )
self.log.debug " tuple expression (%p=%p)-> item component" %
[ attribute, value ]
case
when attribute.to_s.index( ':' )
raise NotImplementedError, "extensible filters are not yet supported"
when value == '*'
return Treequel::Filter::PresentItemComponent.new( attribute )
when value =~ LDAP_SUBSTRING_FILTER_VALUE
return Treequel::Filter::SubstringItemComponent.new( attribute, value )
else
return Treequel::Filter::SimpleItemComponent.new( attribute, value )
end
end
Break down the given expression
as a logical (AND, OR, or NOT)
filter component and return it.
# File lib/treequel/filter.rb, line 568
def self::parse_logical_array_expression( op, *components )
self.log.debug "Parsing logical %p expression with components: %p" %
[ op, components ]
compclass = LOGICAL_COMPONENTS[ op ] or
raise "don't know what a %p condition is. I only know about: %p" %
[ op, LOGICAL_COMPONENTS.keys ]
filterlist = components.collect do |filterexp|
self.log.debug " making %p into a component" % [ filterexp ]
Treequel::Filter.new( filterexp )
end.flatten
return compclass.new( *filterlist )
end
Parse a Sequel::SQL::Expression as a Treequel::Filter::Component and return it.
# File lib/treequel/filter.rb, line 604
def self::parse_sequel_expression( expression )
self.log.debug " parsing Sequel expression: %p" % [ expression ]
if expression.respond_to?( :op )
op = expression.op.to_s.downcase.to_sym
if equivalent = SEQUEL_FILTERTYPE_EQUIVALENTS[ op ]
attribute, value = *expression.args
# Turn :sn.like( 'bob' ) into (cn~=bob) 'cause it has no asterisks
if op == :like
if value.index( '*' )
self.log.debug " turning a LIKE expression with an asterisk into a substring filter"
return Treequel::Filter::SubstringItemComponent.new( attribute, value )
else
self.log.debug " turning a LIKE expression with no wildcards into an 'approx' filter"
equivalent = :approx
end
end
return Treequel::Filter::SimpleItemComponent.new( attribute, value, equivalent )
elsif op == :'!='
contents = Treequel::Filter.new( expression.args )
return Treequel::Filter::NotComponent.new( contents )
elsif op == :'not like'
self.log.debug " making a NOT LIKE expression out of: %p" % [ expression ]
attribute, value = *expression.args
component = nil
if value.index( '*' )
component = Treequel::Filter::SubstringItemComponent.new( attribute, value )
else
component = Treequel::Filter::SimpleItemComponent.new( attribute, value, :approx )
end
filter = Treequel::Filter.new( component )
return Treequel::Filter::NotComponent.new( filter )
elsif LOGICAL_COMPONENTS.key?( op )
components = expression.args.collect do |comp|
Treequel::Filter.new( comp )
end
return self.parse_logical_array_expression( op, components )
elsif msg = UNSUPPORTED_SEQUEL_FILTERTYPES[ op ]
raise Treequel::ExpressionError,
"unsupported Sequel filter syntax %p: %s" %
[ expression, msg ]
else
raise ScriptError,
" unhandled Sequel BooleanExpression: add handling for %p: %p" % [ op, expression ]
end
else
raise Treequel::ExpressionError,
"don't know how to turn %p into a component" % [ expression ]
end
end
Parse a tuple of the form: [ Symbol, Object ] into a Treequel::Filter::Component and return it.
# File lib/treequel/filter.rb, line 539
def self::parse_tuple_array_expression( expression )
self.log.debug "Parsing tuple Array expression %p" % [ expression ]
case expression[1]
# [ :and/:or/:not, [:uid, 1] ] := (&/|/!(uid=1))
# [ :and/:or/:not, {:uid => 1} ] := (&/|/!(uid=1))
when Array, Hash
return self.parse_logical_array_expression( *expression )
when Range
self.log.debug " two ANDed item expressions from a Range"
attribute = expression[0]
range = expression[1]
left = "#{attribute}>=#{range.begin}"
right = "#{attribute}<=#{range.exclude_end? ? range.max : range.end}"
return self.parse_logical_array_expression( :and, [left, right] )
# [ :attribute, 'value' ] := '(attribute=value)'
# when String, Symbol, Numeric, Time
else
self.log.debug " item expression from a %p" % [ expression[1].class ]
return self.parse_item_component( *expression )
end
end
Return a new Filter that is the AND filter of the
receiver with other_filter
.
# File lib/treequel/filter.rb, line 730
def &( other_filter )
return other_filter if self.promiscuous?
return self.dup if other_filter.promiscuous?
return self.class.new( :and, [self, other_filter] )
end
Equality operator – returns true
if other_filter
is equivalent to the receiver.
# File lib/treequel/filter.rb, line 724
def ==( other_filter )
return ( self.component == other_filter.component )
end
Return a human-readable string representation of the filter suitable for debugging.
# File lib/treequel/filter.rb, line 705
def inspect
return %Q{#<%s:0x%0x (%s)>} % [
self.class.name,
self.object_id * 2,
self.component,
]
end
Returns true
if the filter contains a single 'present'
component for the objectClass attribute (which will match every entry)
# File lib/treequel/filter.rb, line 716
def promiscuous?
return self.component.promiscuous?
end
Return the Treequel::Branchset::Filter as a String.
# File lib/treequel/filter.rb, line 692
def to_s
# self.log.debug "stringifying filter %p" % [ self ]
filtercomp = self.component.to_s
if filtercomp[0] == ?(
return filtercomp
else
return '(' + filtercomp + ')'
end
end
Return a new Filter that is the OR filter of the
receiver with other_filter
.
# File lib/treequel/filter.rb, line 739
def |( other_filter )
return other_filter if self.promiscuous?
return self.dup if other_filter.promiscuous?
# Collapse nested ORs into a single one with an additional alternation
# if possible.
if self.component.respond_to?( :add_alternation )
self.log.debug "collapsing nested ORs..."
newcomp = self.component.dup
newcomp.add_alternation( other_filter )
return self.class.new( newcomp )
else
return self.class.new( :or, [self, other_filter] )
end
end