10008: Move test setup from __init__() to setUp()
[arvados.git] / services / api / lib / load_param.rb
index 8d5a9d21ee20d39bb9673d5e4671dfbdc97e3a2e..5b22274d07781325276b5df152037a5b2a13dc61 100644 (file)
@@ -6,7 +6,7 @@
 #   @where, @filters, @limit, @offset, @orders
 module LoadParam
 
-  # Default limit on number of rows to return in a single query.
+  # Default number of rows to return in a single query.
   DEFAULT_LIMIT = 100
 
   # Load params[:where] into @where
@@ -17,7 +17,7 @@ module LoadParam
       @where = params[:where]
     elsif params[:where].is_a? String
       begin
-        @where = Oj.load(params[:where])
+        @where = Oj.strict_load(params[:where])
         raise unless @where.is_a? Hash
       rescue
         raise ArgumentError.new("Could not parse \"where\" param as an object")
@@ -33,7 +33,7 @@ module LoadParam
       @filters += params[:filters]
     elsif params[:filters].is_a? String and !params[:filters].empty?
       begin
-        f = Oj.load params[:filters]
+        f = Oj.strict_load params[:filters]
         if not f.nil?
           raise unless f.is_a? Array
           @filters += f
@@ -44,10 +44,6 @@ module LoadParam
     end
   end
 
-  def default_orders
-    ["#{table_name}.modified_at desc"]
-  end
-
   # Load params[:limit], params[:offset] and params[:order]
   # into @limit, @offset, @orders
   def load_limit_offset_order_params
@@ -55,7 +51,8 @@ module LoadParam
       unless params[:limit].to_s.match(/^\d+$/)
         raise ArgumentError.new("Invalid value for limit parameter")
       end
-      @limit = params[:limit].to_i
+      @limit = [params[:limit].to_i,
+                Rails.configuration.max_items_per_response].min
     else
       @limit = DEFAULT_LIMIT
     end
@@ -75,7 +72,7 @@ module LoadParam
       (case params[:order]
        when String
          if params[:order].starts_with? '['
-           od = Oj.load(params[:order])
+           od = Oj.strict_load(params[:order])
            raise unless od.is_a? Array
            od
          else
@@ -110,8 +107,34 @@ module LoadParam
       end
     end
 
-    if @orders.empty?
-      @orders = default_orders
+    # If the client-specified orders don't amount to a full ordering
+    # (e.g., [] or ['owner_uuid desc']), fall back on the default
+    # orders to ensure repeating the same request (possibly with
+    # different limit/offset) will return records in the same order.
+    #
+    # Clean up the resulting list of orders such that no column
+    # uselessly appears twice (Postgres might not optimize this out
+    # for us) and no columns uselessly appear after a unique column
+    # (Postgres does not optimize this out for us; as of 9.2, "order
+    # by id, modified_at desc, uuid" is slow but "order by id" is
+    # fast).
+    orders_given_and_default = @orders + model_class.default_orders
+    order_cols_used = {}
+    @orders = []
+    orders_given_and_default.each do |order|
+      otablecol = order.split(' ')[0]
+
+      next if order_cols_used[otablecol]
+      order_cols_used[otablecol] = true
+
+      @orders << order
+
+      otable, ocol = otablecol.split('.')
+      if otable == table_name and model_class.unique_columns.include?(ocol)
+        # we already have a full ordering; subsequent entries would be
+        # superfluous
+        break
+      end
     end
 
     case params[:select]
@@ -119,7 +142,7 @@ module LoadParam
       @select = params[:select]
     when String
       begin
-        @select = Oj.load params[:select]
+        @select = Oj.strict_load params[:select]
         raise unless @select.is_a? Array or @select.nil?
       rescue
         raise ArgumentError.new("Could not parse \"select\" param as an array")