Merge branch 'master' into 3036-collection-uuids
[arvados.git] / services / api / lib / record_filters.rb
index d7e556b197323e60ee6d8d95ecce56c947c9a3a3..f31875b47d9fc6cf3bfcee4932c426b2fe5c6b29 100644 (file)
@@ -8,7 +8,7 @@
 module RecordFilters
 
   # Input:
-  # +filters+  Arvados filters as list of lists.
+  # +filters+        array of conditions, each being [column, operator, operand]
   # +ar_table_name+  name of SQL table
   #
   # Output:
@@ -29,26 +29,52 @@ module RecordFilters
         raise ArgumentError.new("Invalid attribute '#{attr}' in filter")
       end
       case operator.downcase
-      when '=', '<', '<=', '>', '>=', 'like'
+      when '=', '<', '<=', '>', '>=', '!=', 'like'
+        attr_type = model_class.attribute_column(attr).type
+        operator = '<>' if operator == '!='
         if operand.is_a? String
+          if attr_type == :boolean
+            if not ['=', '<>'].include?(operator)
+              raise ArgumentError.new("Invalid operator '#{operator}' for " \
+                                      "boolean attribute '#{attr}'")
+            end
+            case operand.downcase
+            when '1', 't', 'true', 'y', 'yes'
+              operand = true
+            when '0', 'f', 'false', 'n', 'no'
+              operand = false
+            else
+              raise ArgumentError("Invalid operand '#{operand}' for " \
+                                  "boolean attribute '#{attr}'")
+            end
+          end
           cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
           if (# any operator that operates on value rather than
               # representation:
-              operator.match(/[<=>]/) and
-              model_class.attribute_column(attr).type == :datetime)
+              operator.match(/[<=>]/) and (attr_type == :datetime))
             operand = Time.parse operand
           end
           param_out << operand
         elsif operand.nil? and operator == '='
           cond_out << "#{ar_table_name}.#{attr} is null"
+        elsif operand.nil? and operator == '<>'
+          cond_out << "#{ar_table_name}.#{attr} is not null"
+        elsif (attr_type == :boolean) and ['=', '<>'].include?(operator) and
+            [true, false].include?(operand)
+          cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+          param_out << operand
         else
           raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
                                   "for '#{operator}' operator in filters")
         end
-      when 'in'
+      when 'in', 'not in'
         if operand.is_a? Array
-          cond_out << "#{ar_table_name}.#{attr} IN (?)"
+          cond_out << "#{ar_table_name}.#{attr} #{operator} (?)"
           param_out << operand
+          if operator == 'not in' and not operand.include?(nil)
+            # explicitly allow NULL
+            cond_out[-1] = "(#{cond_out[-1]} OR #{ar_table_name}.#{attr} IS NULL)"
+          end
         else
           raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
                                   "for '#{operator}' operator in filters")