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 into Treequel equivalents

UNSUPPORTED_SEQUEL_FILTERTYPES

A list of filtertypes that come in as Sequel::Expressions; these generated nicer exception messages that just 'unknown filtertype'

Attributes

component[RW]

The filtercomp part of the filter

Public Class Methods

new( *expression_parts ) click to toggle source

Create a new Treequel::Branchset::Filter with the specified expression.

# File lib/treequel/filter.rb, line 657
def initialize( *expression_parts )
        @component = self.class.parse_expression( expression_parts )
        self.log.debug "created a filter with component: %p" % [ @component ]

        super()
end
parse_array_expression( expression ) click to toggle source

Turn the specified expression Array into a Treequel::Filter::Component object and return it.

# File lib/treequel/filter.rb, line 449
def self::parse_array_expression( expression )
        Treequel.logger.debug "Parsing Array expression %p" % [ expression ]

        case

        # [ ] := '(objectClass=*)'
        when expression.empty?
                Treequel.logger.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) }
                Treequel.logger.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
parse_expression( expression ) click to toggle source

Turn the specified filter expression into a Treequel::Filter::Component object and return it.

# File lib/treequel/filter.rb, line 410
def self::parse_expression( expression )
        Treequel.logger.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_hash_expression( expression ) click to toggle source

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 496
def self::parse_hash_expression( expression )
        Treequel.logger.debug "Parsing Hash expression %p" % [ expression ]

        filterlist = expression.collect do |key, expr|
                Treequel.logger.debug "  adding %p => %p to the filter list" % [ key, expr ]
                if expr.respond_to?( :fetch )
                        Treequel.logger.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
                        Treequel.logger.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_item_component( attribute, value ) click to toggle source

Parse an item component from the specified attribute and value

# File lib/treequel/filter.rb, line 569
def self::parse_item_component( attribute, value )
        Treequel.logger.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
parse_logical_array_expression( op, *components ) click to toggle source

Break down the given expression as a logical (AND, OR, or NOT) filter component and return it.

# File lib/treequel/filter.rb, line 551
def self::parse_logical_array_expression( op, *components )
        Treequel.logger.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|
                Treequel.logger.debug "  making %p into a component" % [ filterexp ]
                Treequel::Filter.new( filterexp )
        end.flatten

        return compclass.new( *filterlist )
end
parse_sequel_expression( expression ) click to toggle source

Parse a Sequel::SQL::Expression as a Treequel::Filter::Component and return it.

# File lib/treequel/filter.rb, line 587
def self::parse_sequel_expression( expression )
        Treequel.logger.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( '*' )
                                        Treequel.logger.debug                                                          "    turning a LIKE expression with an asterisk into a substring filter"
                                        return Treequel::Filter::SubstringItemComponent.new( attribute, value )
                                else
                                        Treequel.logger.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'
                        Treequel.logger.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_tuple_array_expression( expression ) click to toggle source

Parse a tuple of the form: [ Symbol, Object ] into a Treequel::Filter::Component and return it.

# File lib/treequel/filter.rb, line 522
def self::parse_tuple_array_expression( expression )
        Treequel.logger.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
                Treequel.logger.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
                Treequel.logger.debug "  item expression from a %p" % [ expression[1].class ]
                return self.parse_item_component( *expression )
        end
end

Public Instance Methods

&( other_filter ) click to toggle source

Return a new Filter that is the AND filter of the receiver with other_filter.

# File lib/treequel/filter.rb, line 712
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
Also aliased as: +
+( other_filter ) click to toggle source
Alias for: &
==( other_filter ) click to toggle source

Equality operator -- returns true if other_filter is equivalent to the receiver.

# File lib/treequel/filter.rb, line 706
def ==( other_filter )
        return ( self.component == other_filter.component )
end
inspect() click to toggle source

Return a human-readable string representation of the filter suitable for debugging.

# File lib/treequel/filter.rb, line 687
def inspect
        return %Q{#<%s:0x%0x (%s)>} % [
                self.class.name,
                self.object_id * 2,
                self.component,
        ]
end
is_promiscuous?() click to toggle source
Alias for: promiscuous?
promiscuous?() click to toggle source

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 698
def promiscuous?
        return self.component.promiscuous?
end
Also aliased as: is_promiscuous?
to_s() click to toggle source

Return the Treequel::Branchset::Filter as a String.

# File lib/treequel/filter.rb, line 674
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
|( other_filter ) click to toggle source

Return a new Filter that is the OR filter of the receiver with other_filter.

# File lib/treequel/filter.rb, line 721
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