From 9ec15072e6631578584bd9c08d26025e807a8d48 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Tue, 12 Dec 2017 10:32:15 -0500 Subject: [PATCH] 4019: Add URI quoting option for keys. Update docs. Fix tests. Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- doc/api/methods.html.textile.liquid | 2 +- services/api/app/models/arvados_model.rb | 2 +- services/api/lib/record_filters.rb | 26 ++++++++++++++----- services/api/test/fixtures/collections.yml | 14 ++++++++++ .../functional/arvados/v1/filters_test.rb | 3 ++- services/api/test/unit/arvados_model_test.rb | 2 +- 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/doc/api/methods.html.textile.liquid b/doc/api/methods.html.textile.liquid index 6f7eca72c8..00c120d9f8 100644 --- a/doc/api/methods.html.textile.liquid +++ b/doc/api/methods.html.textile.liquid @@ -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.@. +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.@. 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| diff --git a/services/api/app/models/arvados_model.rb b/services/api/app/models/arvados_model.rb index 72db306f3a..08d7e9345d 100644 --- a/services/api/app/models/arvados_model.rb +++ b/services/api/app/models/arvados_model.rb @@ -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 diff --git a/services/api/lib/record_filters.rb b/services/api/lib/record_filters.rb index 56e16495ee..900b784cd0 100644 --- a/services/api/lib/record_filters.rb +++ b/services/api/lib/record_filters.rb @@ -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 diff --git a/services/api/test/fixtures/collections.yml b/services/api/test/fixtures/collections.yml index 871a3578cc..ea87cca36f 100644 --- a/services/api/test/fixtures/collections.yml +++ b/services/api/test/fixtures/collections.yml @@ -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 diff --git a/services/api/test/functional/arvados/v1/filters_test.rb b/services/api/test/functional/arvados/v1/filters_test.rb index 18f3c6b947..677a5f1f86 100644 --- a/services/api/test/functional/arvados/v1/filters_test.rb +++ b/services/api/test/functional/arvados/v1/filters_test.rb @@ -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], []], + ['', '=', "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 diff --git a/services/api/test/unit/arvados_model_test.rb b/services/api/test/unit/arvados_model_test.rb index 923083832c..d74cbc5dab 100644 --- a/services/api/test/unit/arvados_model_test.rb +++ b/services/api/test/unit/arvados_model_test.rb @@ -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) -- 2.30.2