From 534219f05e2d1ddafd2e296cba21531b41db6c92 Mon Sep 17 00:00:00 2001 From: Benjamin Vetter Date: Wed, 31 Jan 2024 08:39:39 +0100 Subject: [PATCH] Support nested json --- docker-compose.yml | 1 + lib/search_cop/visitors/mysql.rb | 2 +- lib/search_cop/visitors/postgres.rb | 10 +++++++--- lib/search_cop_grammar/attributes.rb | 12 ++++++------ test/string_test.rb | 18 ++++++++++++++++++ test/test_helper.rb | 7 +++++-- 6 files changed, 38 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1e472df..a88eb13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_ROOT_PASSWORD= + - MYSQL_DATABASE=search_cop ports: - 3306:3306 postgres: diff --git a/lib/search_cop/visitors/mysql.rb b/lib/search_cop/visitors/mysql.rb index aa28838..1c64ca2 100644 --- a/lib/search_cop/visitors/mysql.rb +++ b/lib/search_cop/visitors/mysql.rb @@ -4,7 +4,7 @@ module Mysql # rubocop:disable Naming/MethodName def visit_SearchCopGrammar_Attributes_Json(attribute) - "#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}->#{quote "$.#{attribute.field_name}"}" + "#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}->#{quote "$.#{attribute.field_names.join(".")}"}" end class FulltextQuery < Visitor diff --git a/lib/search_cop/visitors/postgres.rb b/lib/search_cop/visitors/postgres.rb index 263a21f..f35064b 100644 --- a/lib/search_cop/visitors/postgres.rb +++ b/lib/search_cop/visitors/postgres.rb @@ -4,15 +4,19 @@ module Postgres # rubocop:disable Naming/MethodName def visit_SearchCopGrammar_Attributes_Json(attribute) - "#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}->>#{quote attribute.field_name}" + elements = ["#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}", *attribute.field_names.map { |field_name| quote(field_name) }] + + "#{elements[0...-1].join("->")}->>#{elements.last}" end def visit_SearchCopGrammar_Attributes_Jsonb(attribute) - "#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}->>#{quote attribute.field_name}" + elements = ["#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}", *attribute.field_names.map { |field_name| quote(field_name) }] + + "#{elements[0...-1].join("->")}->>#{elements.last}" end def visit_SearchCopGrammar_Attributes_Hstore(attribute) - "#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}->#{quote attribute.field_name}" + "#{quote_table_name attribute.table_alias}.#{quote_column_name attribute.column_name}->#{quote attribute.field_names.first}" end class FulltextQuery < Visitor diff --git a/lib/search_cop_grammar/attributes.rb b/lib/search_cop_grammar/attributes.rb index 8f89cf0..513a8fb 100644 --- a/lib/search_cop_grammar/attributes.rb +++ b/lib/search_cop_grammar/attributes.rb @@ -88,13 +88,13 @@ def alias_for(name) def attribute_for(attribute_definition) query_info.references.push attribute_definition - table, column_with_field = attribute_definition.split(".") - column, field = column_with_field.split("->") + table, column_with_fields = attribute_definition.split(".") + column, *fields = column_with_fields.split("->") klass = klass_for(table) raise(SearchCop::UnknownAttribute, "Unknown attribute #{attribute_definition}") unless klass.columns_hash[column] - Attributes.const_get(klass.columns_hash[column].type.to_s.classify).new(klass, alias_for(table), column, field, options) + Attributes.const_get(klass.columns_hash[column].type.to_s.classify).new(klass, alias_for(table), column, fields, options) end def generator_for(name) @@ -111,14 +111,14 @@ def generators end class Base - attr_reader :attribute, :table_alias, :column_name, :field_name, :options + attr_reader :attribute, :table_alias, :column_name, :field_names, :options - def initialize(klass, table_alias, column_name, field_name, options = {}) + def initialize(klass, table_alias, column_name, field_names, options = {}) @attribute = klass.arel_table.alias(table_alias)[column_name] @klass = klass @table_alias = table_alias @column_name = column_name - @field_name = field_name + @field_names = field_names @options = (options || {}) end diff --git a/test/string_test.rb b/test/string_test.rb index 15b3dfe..999ad1d 100644 --- a/test/string_test.rb +++ b/test/string_test.rb @@ -149,6 +149,15 @@ def test_jsonb refute_includes Product.search("jsonb_name: rejected"), product end + def test_nested_jsonb + return if DATABASE != "postgres" + + product = create(:product, nested_jsonb: { nested: { name: "expected" } }) + + assert_includes Product.search("nested_jsonb_name: expected"), product + refute_includes Product.search("nested_jsonb_name: rejected"), product + end + def test_json return if DATABASE != "postgres" && DATABASE != "mysql" @@ -158,6 +167,15 @@ def test_json refute_includes Product.search("json_name: rejected"), product end + def test_nested_json + return if DATABASE != "postgres" && DATABASE != "mysql" + + product = create(:product, nested_json: { nested: { name: "expected" } }) + + assert_includes Product.search("nested_json_name: expected"), product + refute_includes Product.search("nested_json_name: rejected"), product + end + def test_hstore return if DATABASE != "postgres" diff --git a/test/test_helper.rb b/test/test_helper.rb index 1b13894..f8cd43b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -49,13 +49,13 @@ class Product < ActiveRecord::Base end if DATABASE == "postgres" - attributes json_name: "json->name", jsonb_name: "jsonb->name", hstore_name: "hstore->name" + attributes nested_json_name: "nested_json->nested->name", nested_jsonb_name: "nested_jsonb->nested->name", json_name: "json->name", jsonb_name: "jsonb->name", hstore_name: "hstore->name" options :title, dictionary: "english" end if DATABASE == "mysql" - attributes json_name: "json->name" + attributes nested_json_name: "nested_json->nested->name", json_name: "json->name" end generator :custom_eq do |column_name, raw_value| @@ -137,12 +137,15 @@ class AvailableProduct < Product if DATABASE == "postgres" t.json :json + t.json :nested_json t.jsonb :jsonb + t.jsonb :nested_jsonb t.hstore :hstore end if DATABASE == "mysql" t.json :json + t.json :nested_json end end