11453: Merge branch 'master' into 11453-federated-tokens
[arvados.git] / services / api / lib / record_filters.rb
index 51153fe081267162139a3ec0dbadbe49bfd1b1eb..eb8d09b74c88e118af371f35ab58d15bb1a704b7 100644 (file)
@@ -1,3 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 # Mixin module providing a method to convert filters into a list of SQL
 # fragments suitable to be fed to ActiveRecord #where.
 #
@@ -37,19 +41,23 @@ module RecordFilters
 
       cond_out = []
 
-      if operator == '@@' # full-text-search
+      if operator == '@@'
+        # Full-text search
         if attrs_in != 'any'
           raise ArgumentError.new("Full text search on individual columns is not supported")
         end
-        attrs = [] #  skip the generic per-column operator loop below
-        # Use to_tsquery since plainto_tsquery does not support prefix search.
-        # Instead split operand and join the words with ' & ' and add ':*' to the last word
-        # Thus when searched for "some str", objects containing "some" and "str:*" are found.
+        if operand.is_a? Array
+          raise ArgumentError.new("Full text search not supported for array operands")
+        end
+
+        # Skip the generic per-column operator loop below
+        attrs = []
+        # Use to_tsquery since plainto_tsquery does not support prefix
+        # search. And, split operand and join the words with ' & '
         cond_out << model_class.full_text_tsvector+" @@ to_tsquery(?)"
-        operand << ':*'
         param_out << operand.split.join(' & ')
-      else
-       attrs.each do |attr|
+      end
+      attrs.each do |attr|
         if !model_class.searchable_columns(operator).index attr.to_s
           raise ArgumentError.new("Invalid attribute '#{attr}' in filter")
         end
@@ -73,7 +81,12 @@ module RecordFilters
                                     "boolean attribute '#{attr}'")
               end
             end
-            cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+            if operator == '<>'
+              # explicitly allow NULL
+              cond_out << "#{ar_table_name}.#{attr} #{operator} ? OR #{ar_table_name}.#{attr} IS NULL"
+            else
+              cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+            end
             if (# any operator that operates on value rather than
                 # representation:
                 operator.match(/[<=>]/) and (attr_type == :datetime))
@@ -117,8 +130,9 @@ module RecordFilters
             end
           end
           cond_out << cond.join(' OR ')
+        else
+          raise ArgumentError.new("Invalid operator '#{operator}'")
         end
-       end
       end
       conds_out << cond_out.join(' OR ') if cond_out.any?
     end