4019: Add URI quoting option for keys. Update docs. Fix tests.
authorPeter Amstutz <pamstutz@veritasgenetics.com>
Tue, 12 Dec 2017 15:32:15 +0000 (10:32 -0500)
committerPeter Amstutz <pamstutz@veritasgenetics.com>
Tue, 12 Dec 2017 15:32:15 +0000 (10:32 -0500)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

doc/api/methods.html.textile.liquid
services/api/app/models/arvados_model.rb
services/api/lib/record_filters.rb
services/api/test/fixtures/collections.yml
services/api/test/functional/arvados/v1/filters_test.rb
services/api/test/unit/arvados_model_test.rb

index 6f7eca72c8ec6fe58d043bc434774e7922e64225..00c120d9f8f1be4aad90022b514fe37024618dc3 100644 (file)
@@ -100,7 +100,7 @@ table(table table-bordered table-condensed).
 
 h4. Filtering on subproperties
 
-Some record type have an additional @properties@ attribute that allows recording and filtering on additional key-value pairs.  To filter on a subproperty, the value in the @attribute@ position has the form @properties.<user_property>@.
+Some record type have an additional @properties@ attribute that allows recording and filtering on additional key-value pairs.  To filter on a subproperty, the value in the @attribute@ position has the form @properties.user_property@.  You may also use JSON-LD / RDF style URIs for property keys by enclosing them in @<...>@ for example @properties.<http://example.com/user_property>@.  Alternately you may also provide a JSON-LD "@context" field, however at this time JSON-LD contexts are not interpreted by Arvados.
 
 table(table table-bordered table-condensed).
 |_. Operator|_. Operand type|_. Description|_. Example|
index 72db306f3a217c7eebc4c451d84726f03b58df7b..08d7e9345dbb8178d6891821277e6a01958aeacd 100644 (file)
@@ -132,7 +132,7 @@ class ArvadosModel < ActiveRecord::Base
     textonly_operator = !operator.match(/[<=>]/)
     self.columns.select do |col|
       case col.type
-      when :string, :text, :jsonb
+      when :string, :text
         true
       when :datetime, :integer, :boolean
         !textonly_operator
index 56e16495eecd2706f4941e92c1e588267758dbb4..900b784cd03283ba86540754cdd6324514800f1c 100644 (file)
@@ -63,11 +63,17 @@ module RecordFilters
       attrs.each do |attr|
         subproperty = attr.split(".", 2)
 
-        if !model_class.searchable_columns(operator).index subproperty[0]
-          raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' in filter")
-        end
+        col = model_class.columns.select { |c| c.name == subproperty[0] }.first
 
         if subproperty.length == 2
+          if col.type != :jsonb
+            raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' for operator '#{operator}' in filter")
+          end
+
+          if subproperty[1][0] == "<" and subproperty[1][-1] == ">"
+            subproperty[1] = subproperty[1][1..-2]
+          end
+
         # jsonb search
           case operator.downcase
           when '=', '!='
@@ -112,7 +118,18 @@ module RecordFilters
           else
             raise ArgumentError.new("Invalid operator for subproperty search '#{operator}'")
           end
+        elsif operator.downcase == "exists"
+          if col.type != :jsonb
+            raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' for operator '#{operator}' in filter")
+          end
+
+          cond_out << "jsonb_exists(#{ar_table_name}.#{subproperty[0]}, ?)"
+          param_out << operand
         else
+          if !model_class.searchable_columns(operator).index subproperty[0]
+            raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' in filter")
+          end
+
           case operator.downcase
           when '=', '<', '<=', '>', '>=', '!=', 'like', 'ilike'
             attr_type = model_class.attribute_column(attr).type
@@ -185,9 +202,6 @@ module RecordFilters
               end
             end
             cond_out << cond.join(' OR ')
-          when 'exists'
-            cond_out << "jsonb_exists(#{ar_table_name}.#{subproperty[0]}, ?)"
-            param_out << operand
           else
             raise ArgumentError.new("Invalid operator '#{operator}'")
           end
index 871a3578ccd88230324e6d260e393f0b4f932256..ea87cca36fb5d266ad1bf7a93ae690e5783f6747 100644 (file)
@@ -799,6 +799,20 @@ collection_with_prop2_5:
   properties:
     prop2: 5
 
+collection_with_uri_prop:
+  uuid: zzzzz-4zz18-withuripropval1
+  portable_data_hash: fa7aeb5140e2848d39b416daeef4ffc5+45
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2015-02-13T17:22:54Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2015-02-13T17:22:54Z
+  updated_at: 2015-02-13T17:22:54Z
+  manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
+  name: collection with RDF-style URI property key
+  properties:
+    "http://schema.org/example": "value1"
+
 # Test Helper trims the rest of the file
 
 # Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper
index 18f3c6b9471668459a14467f1d21e8b7a14372b0..677a5f1f8662c22254220ffd497e0d52efb2abd9 100644 (file)
@@ -170,7 +170,8 @@ class Arvados::V1::FiltersTest < ActionController::TestCase
    ['prop2', '>',  1, [:collection_with_prop2_5], [:collection_with_prop2_1]],
    ['prop2', '<',  5, [:collection_with_prop2_1], [:collection_with_prop2_5]],
    ['prop2', '<=', 5, [:collection_with_prop2_1, :collection_with_prop2_5], []],
-   ['prop2', '>=', 1, [:collection_with_prop2_1, :collection_with_prop2_5], []]
+   ['prop2', '>=', 1, [:collection_with_prop2_1, :collection_with_prop2_5], []],
+   ['<http://schema.org/example>', '=', "value1", [:collection_with_uri_prop], []],
   ].each do |prop, op, opr, inc, ex|
     test "jsonb filter properties.#{prop} #{op} #{opr})" do
       @controller = Arvados::V1::CollectionsController.new
index 923083832c658627f02b6001b8f71ccfd47f6a59..d74cbc5dab7c8be7209d92db7a864369e0016b9a 100644 (file)
@@ -147,7 +147,7 @@ class ArvadosModelTest < ActiveSupport::TestCase
         search_index_columns = table_class.searchable_columns('ilike')
         # Disappointing, but text columns aren't indexed yet.
         search_index_columns -= table_class.columns.select { |c|
-          c.type == :text or c.name == 'description' or c.name == 'file_names'
+          c.type == :text or c.name == 'description' or c.name == 'file_names' or c.name == 'properties'
         }.collect(&:name)
 
         indexes = ActiveRecord::Base.connection.indexes(table)