2873: add /permissions API method
[arvados.git] / services / api / lib / record_filters.rb
1 # Mixin module providing a method to convert filters into a list of SQL
2 # fragments suitable to be fed to ActiveRecord #where.
3 #
4 # Expects:
5 #   model_class
6 # Operates on:
7 #   @objects
8 module RecordFilters
9
10   # Input:
11   # +filters+        array of conditions, each being [column, operator, operand]
12   # +ar_table_name+  name of SQL table
13   #
14   # Output:
15   # Hash with two keys:
16   # :cond_out  array of SQL fragments for each filter expression
17   # :param_out  array of values for parameter substitution in cond_out
18   def record_filters filters, ar_table_name
19     cond_out = []
20     param_out = []
21
22     filters.each do |filter|
23       attr, operator, operand = filter
24       if !filter.is_a? Array
25         raise ArgumentError.new("Invalid element in filters array: #{filter.inspect} is not an array")
26       elsif !operator.is_a? String
27         raise ArgumentError.new("Invalid operator '#{operator}' (#{operator.class}) in filter")
28       elsif !model_class.searchable_columns(operator).index attr.to_s
29         raise ArgumentError.new("Invalid attribute '#{attr}' in filter")
30       end
31       case operator.downcase
32       when '=', '<', '<=', '>', '>=', '!=', 'like'
33         if operand.is_a? String
34           if operator == '!='
35             operator = '<>'
36           end
37           cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
38           if (# any operator that operates on value rather than
39               # representation:
40               operator.match(/[<=>]/) and
41               model_class.attribute_column(attr).type == :datetime)
42             operand = Time.parse operand
43           end
44           param_out << operand
45         elsif operand.nil? and operator == '='
46           cond_out << "#{ar_table_name}.#{attr} is null"
47         elsif operand.nil? and operator == '!='
48           cond_out << "#{ar_table_name}.#{attr} is not null"
49         else
50           raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
51                                   "for '#{operator}' operator in filters")
52         end
53       when 'in', 'not in'
54         if operand.is_a? Array
55           cond_out << "#{ar_table_name}.#{attr} #{operator} (?)"
56           param_out << operand
57           if operator == 'not in' and not operand.include?(nil)
58             # explicitly allow NULL
59             cond_out[-1] = "(#{cond_out[-1]} OR #{ar_table_name}.#{attr} IS NULL)"
60           end
61         else
62           raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
63                                   "for '#{operator}' operator in filters")
64         end
65       when 'is_a'
66         operand = [operand] unless operand.is_a? Array
67         cond = []
68         operand.each do |op|
69           cl = ArvadosModel::kind_class op
70           if cl
71             cond << "#{ar_table_name}.#{attr} like ?"
72             param_out << cl.uuid_like_pattern
73           else
74             cond << "1=0"
75           end
76         end
77         cond_out << cond.join(' OR ')
78       end
79     end
80
81     {:cond_out => cond_out, :param_out => param_out}
82   end
83
84 end