Filter

class
Superclass
Object
Included Modules
Treequel::Constants::Patterns
Extended With
Loggability

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]

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

anchor
new( *expression_parts )

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
anchor
parse_array_expression( expression )

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
anchor
parse_expression( expression )

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
anchor
parse_hash_expression( expression )

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
anchor
parse_item_component( attribute, value )

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
anchor
parse_logical_array_expression( op, *components )

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
anchor
parse_sequel_expression( expression )

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
anchor
parse_tuple_array_expression( expression )

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

anchor
&( other_filter )

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
Also aliased as: +
anchor
+( other_filter )
Alias for: &
anchor
==( other_filter )

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
anchor
inspect()

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
anchor
is_promiscuous?()
Alias for: promiscuous?
anchor
promiscuous?()

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
Also aliased as: is_promiscuous?
anchor
to_s()

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
anchor
|( other_filter )

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