class Treequel::Filter
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 = 1*filter 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]
Constants
- DEFAULT_EXPRESSION
The default filter expression to use when searching if none is specified
- LOGICAL_COMPONENTS
The mapping of leftmost symbols in a boolean expression and the corresponding FilterComponent class.
- SEQUEL_FILTERTYPE_EQUIVALENTS
An equivalence mapping of operation names from
Sequel
expressions intoTreequel
equivalents- UNSUPPORTED_SEQUEL_FILTERTYPES
A list of filtertypes that come in as Sequel::Expressions; these generated nicer exception messages that just 'unknown filtertype'
Attributes
The filtercomp part of the filter
Public Class Methods
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
Public Instance Methods
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 %{#<%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