Skip to content
This repository
Browse code

Merge pull request #18 from cj/IN_and_NOT_IN

Added IN and NOT IN using ^ and !^
  • Loading branch information...
commit 29875cecb72fb86ae02afa9fd25d484fbeb98c4b 2 parents a12c2ad + 33d15ff
abenari authored February 14, 2012
18  lib/scoped_search/definition.rb
@@ -149,9 +149,9 @@ def initialize(klass)
149 149
       register_complete_for! unless klass.respond_to?(:complete_for)
150 150
 
151 151
     end
152  
-   
  152
+
153 153
     attr_accessor :profile, :default_order
154  
-    
  154
+
155 155
     def fields
156 156
       @profile ||= :default
157 157
       @profile_fields[@profile] ||= {}
@@ -176,11 +176,11 @@ def field_by_name(name)
176 176
     def operator_by_field_name(name)
177 177
       field = field_by_name(name)
178 178
       return [] if field.nil?
179  
-      return field.operators                        if field.operators
180  
-      return ['= ', '!= ']                          if field.set?
181  
-      return ['= ', '> ', '< ', '<= ', '>= ','!= '] if field.numerical?
182  
-      return ['= ', '!= ', '~ ', '!~ ']             if field.textual?
183  
-      return ['= ', '> ', '< ']                     if field.temporal?
  179
+      return field.operators                                   if field.operators
  180
+      return ['= ', '!= ']                                     if field.set?
  181
+      return ['= ', '> ', '< ', '<= ', '>= ','!= ', '^ ', '!^ '] if field.numerical?
  182
+      return ['= ', '!= ', '~ ', '!~ ', '^ ', '!^ ']             if field.textual?
  183
+      return ['= ', '> ', '< ']                                if field.temporal?
184 184
       raise ScopedSearch::QueryNotSupported, "could not verify '#{name}' type, this can be a result of a definition error"
185 185
     end
186 186
 
@@ -230,8 +230,8 @@ def register_named_scope! # :nodoc
230 230
         when 2
231 231
           @klass.named_scope(:search_for, lambda { |*args| ScopedSearch::QueryBuilder.build_query(self, args[0], args[1]) })
232 232
         when 3
233  
-          @klass.scope(:search_for, lambda { |*args| 
234  
-            find_options = ScopedSearch::QueryBuilder.build_query(self, args[0], args[1]) 
  233
+          @klass.scope(:search_for, lambda { |*args|
  234
+            find_options = ScopedSearch::QueryBuilder.build_query(self, args[0], args[1])
235 235
             search_scope = @klass.scoped
236 236
             search_scope = search_scope.where(find_options[:conditions]) if find_options[:conditions]
237 237
             search_scope = search_scope.includes(find_options[:include]) if find_options[:include]
18  lib/scoped_search/query_builder.rb
@@ -101,7 +101,8 @@ def order_by(order, &block)
101 101
 
102 102
     # A hash that maps the operators of the query language with the corresponding SQL operator.
103 103
     SQL_OPERATORS = { :eq =>'=',  :ne => '<>', :like => 'LIKE', :unlike => 'NOT LIKE',
104  
-                      :gt => '>', :lt =>'<',   :lte => '<=',    :gte => '>=' }
  104
+                      :gt => '>', :lt =>'<',   :lte => '<=',    :gte => '>=',
  105
+                      :in => 'IN',:notin => 'NOT IN' }
105 106
 
106 107
     # Return the SQL operator to use given an operator symbol and field definition.
107 108
     #
@@ -170,9 +171,16 @@ def datetime_test(field, operator, value, &block) # :yields: finder_option_type,
170 171
     end
171 172
 
172 173
     # Validate the key name is in the set and translate the value to the set value.
  174
+    def translate_value(field, value)
  175
+      translated_value = field.complete_value[value.to_sym]
  176
+      raise ScopedSearch::QueryNotSupported, "'#{field.field}' should be one of '#{field.complete_value.keys.join(', ')}', but the query was '#{value}'" if translated_value.nil?
  177
+      translated_value
  178
+    end
  179
+
  180
+    # A 'set' is group of possible values, for example a status might be "on", "off" or "unknown" and the database representation
  181
+    # could be for example a numeric value. This method will validate the input and translate it into the database representation.
173 182
     def set_test(field, operator,value, &block)
174  
-      set_value = field.complete_value[value.to_sym]
175  
-      raise ScopedSearch::QueryNotSupported, "'#{field.field}' should be one of '#{field.complete_value.keys.join(', ')}', but the query was '#{value}'" if set_value.nil?
  183
+      set_value = translate_value(field, value)
176 184
       raise ScopedSearch::QueryNotSupported, "Operator '#{operator}' not supported for '#{field.field}'" unless [:eq,:ne].include?(operator)
177 185
       negate = ''
178 186
       if [true,false].include?(set_value)
@@ -205,6 +213,10 @@ def sql_test(field, operator, value, lhs, &block) # :yields: finder_option_type,
205 213
       if [:like, :unlike].include?(operator)
206 214
         yield(:parameter, (value !~ /^\%|\*/ && value !~ /\%|\*$/) ? "%#{value}%" : value.tr_s('%*', '%'))
207 215
         return "#{field.to_sql(operator, &block)} #{self.sql_operator(operator, field)} ?"
  216
+      elsif [:in, :notin].include?(operator)
  217
+        value.split(',').collect { |v| yield(:parameter, field.set? ? translate_value(field, v) : v.strip) }
  218
+        value = value.split(',').collect { "?" }.join(",")
  219
+        return "#{field.to_sql(operator, &block)} #{self.sql_operator(operator, field)} (#{value})"
208 220
       elsif field.temporal?
209 221
         return datetime_test(field, operator, value, &block)
210 222
       elsif field.set?
22  lib/scoped_search/query_language/parser.rb
@@ -11,7 +11,7 @@ module ScopedSearch::QueryLanguage::Parser
11 11
   LOGICAL_INFIX_OPERATORS  = [:and, :or]
12 12
   LOGICAL_PREFIX_OPERATORS = [:not]
13 13
   NULL_PREFIX_OPERATORS    = [:null, :notnull]
14  
-  COMPARISON_OPERATORS = [:eq, :ne, :gt, :gte, :lt, :lte, :like, :unlike]
  14
+  COMPARISON_OPERATORS = [:eq, :ne, :gt, :gte, :lt, :lte, :like, :unlike, :in, :notin]
15 15
   ALL_INFIX_OPERATORS = LOGICAL_INFIX_OPERATORS + COMPARISON_OPERATORS
16 16
   ALL_PREFIX_OPERATORS = LOGICAL_PREFIX_OPERATORS + COMPARISON_OPERATORS + NULL_PREFIX_OPERATORS
17 17
 
@@ -99,11 +99,25 @@ def parse_infix_comparison
99 99
     end
100 100
   end
101 101
 
102  
-  # Parses a single value.
  102
+  # Parse values in the format (val, val, val)
  103
+  def parse_multiple_values
  104
+    next_token if  peek_token == :lparen #skip :lparen
  105
+    value = []
  106
+    value << current_token if String === next_token until peek_token.nil? || peek_token == :rparen
  107
+    next_token if peek_token == :rparen  # consume the :rparen
  108
+    value.join(',')
  109
+  end
  110
+
103 111
   # This can either be a constant value or a field name.
104 112
   def parse_value
105  
-    raise ScopedSearch::QueryNotSupported, "Value expected but found #{peek_token.inspect}" unless String === peek_token
106  
-    ScopedSearch::QueryLanguage::AST::LeafNode.new(next_token)
  113
+    if String === peek_token
  114
+      ScopedSearch::QueryLanguage::AST::LeafNode.new(next_token)
  115
+    elsif ([:in, :notin].include? current_token)
  116
+      value = parse_multiple_values()
  117
+      ScopedSearch::QueryLanguage::AST::LeafNode.new(value)
  118
+    else
  119
+      raise ScopedSearch::QueryNotSupported, "Value expected but found #{peek_token.inspect}"
  120
+    end
107 121
   end
108 122
 
109 123
   protected
4  lib/scoped_search/query_language/tokenizer.rb
@@ -7,7 +7,7 @@ module ScopedSearch::QueryLanguage::Tokenizer
7 7
 
8 8
   # Every operator the language supports.
9 9
   OPERATORS = { '&' => :and, '|' => :or, '&&' => :and, '||' => :or, '-'=> :not, '!' => :not, '~' => :like, '!~' => :unlike,
10  
-      '=' => :eq, '==' => :eq, '!=' => :ne, '<>' => :ne, '>' => :gt, '<' => :lt, '>=' => :gte, '<=' => :lte }
  10
+      '=' => :eq, '==' => :eq, '!=' => :ne, '<>' => :ne, '>' => :gt, '<' => :lt, '>=' => :gte, '<=' => :lte, '^' => :in, '!^' => :notin }
11 11
 
12 12
   # Tokenizes the string and returns the result as an array of tokens.
13 13
   def tokenize
@@ -41,7 +41,7 @@ def each_token(&block)
41 41
       when '(';  yield(:lparen)
42 42
       when ')';  yield(:rparen)
43 43
       when ',';  yield(:comma)
44  
-      when /\&|\||=|<|>|!|~|-/;  tokenize_operator(&block)
  44
+      when /\&|\||=|<|>|\^|!|~|-/;  tokenize_operator(&block)
45 45
       when '"';                  tokenize_quoted_keyword(&block)
46 46
       else;                      tokenize_keyword(&block)
47 47
       end

0 notes on commit 29875ce

Please sign in to comment.
Something went wrong with that request. Please try again.