diff --git a/lib/active_record/connection_adapters/clickhouse/oid/map.rb b/lib/active_record/connection_adapters/clickhouse/oid/map.rb index 08067a33..9ae49449 100644 --- a/lib/active_record/connection_adapters/clickhouse/oid/map.rb +++ b/lib/active_record/connection_adapters/clickhouse/oid/map.rb @@ -26,6 +26,8 @@ def type def deserialize(value) if value.is_a?(::Hash) value.map { |k, item| [k.to_s, deserialize(item)] }.to_h + elsif value.is_a?(::Array) + value.map { |item| deserialize(item) } else return value if value.nil? case @subtype @@ -44,6 +46,8 @@ def deserialize(value) def serialize(value) if value.is_a?(::Hash) value.map { |k, item| [k.to_s, serialize(item)] }.to_h + elsif value.is_a?(::Array) + value.map { |item| serialize(item) } else return value if value.nil? case @subtype diff --git a/lib/active_record/connection_adapters/clickhouse/schema_creation.rb b/lib/active_record/connection_adapters/clickhouse/schema_creation.rb index 3d795a2b..d9da9b8c 100644 --- a/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +++ b/lib/active_record/connection_adapters/clickhouse/schema_creation.rb @@ -33,7 +33,10 @@ def add_column_options!(sql, options) if options[:array] sql.gsub!(/\s+(.*)/, ' Array(\1)') end - if options[:map] + if options[:map] == :array + sql.gsub!(/\s+(.*)/, ' Map(String, Array(\1))') + end + if options[:map] == true sql.gsub!(/\s+(.*)/, ' Map(String, \1)') end sql.gsub!(/(\sString)\(\d+\)/, '\1') diff --git a/lib/clickhouse-activerecord/schema_dumper.rb b/lib/clickhouse-activerecord/schema_dumper.rb index 44a2bd8c..23fb0333 100644 --- a/lib/clickhouse-activerecord/schema_dumper.rb +++ b/lib/clickhouse-activerecord/schema_dumper.rb @@ -145,6 +145,10 @@ def schema_array(column) end def schema_map(column) + if column.sql_type =~ /Map\(([^,]+),\s*(Array)\)/ + return :array + end + (column.sql_type =~ /Map?\(/).nil? ? nil : true end @@ -157,6 +161,9 @@ def prepare_column_options(column) spec[:unsigned] = schema_unsigned(column) spec[:array] = schema_array(column) spec[:map] = schema_map(column) + if spec[:map] == :array + spec[:array] = nil + end spec[:low_cardinality] = schema_low_cardinality(column) spec.merge(super).compact end diff --git a/spec/fixtures/migrations/add_array_datetime/1_create_actions_table.rb b/spec/fixtures/migrations/add_array_datetime/1_create_actions_table.rb index 17048fbd..30d7245e 100644 --- a/spec/fixtures/migrations/add_array_datetime/1_create_actions_table.rb +++ b/spec/fixtures/migrations/add_array_datetime/1_create_actions_table.rb @@ -10,4 +10,3 @@ def up end end end - diff --git a/spec/fixtures/migrations/add_map_datetime/1_create_verbs_table.rb b/spec/fixtures/migrations/add_map_datetime/1_create_verbs_table.rb index 59cb8b23..e09f0969 100644 --- a/spec/fixtures/migrations/add_map_datetime/1_create_verbs_table.rb +++ b/spec/fixtures/migrations/add_map_datetime/1_create_verbs_table.rb @@ -4,8 +4,12 @@ def up t.datetime :map_datetime, null: false, map: true t.string :map_string, null: false, map: true t.integer :map_int, null: false, map: true + + t.datetime :map_array_datetime, null: false, map: :array + t.string :map_array_string, null: false, map: :array + t.integer :map_array_int, null: false, map: :array + t.date :date, null: false end end end - diff --git a/spec/fixtures/migrations/dsl_table_with_fixed_string_creation/1_create_some_table.rb b/spec/fixtures/migrations/dsl_table_with_fixed_string_creation/1_create_some_table.rb index b16271ff..99becb77 100644 --- a/spec/fixtures/migrations/dsl_table_with_fixed_string_creation/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_table_with_fixed_string_creation/1_create_some_table.rb @@ -6,6 +6,7 @@ def up t.string :fixed_string1, fixed_string: 1, null: false t.string :fixed_string16_array, fixed_string: 16, array: true, null: true t.string :fixed_string16_map, fixed_string: 16, map: true, null: true + t.string :fixed_string16_map_array, fixed_string: 16, map: :array, null: true end end end diff --git a/spec/fixtures/migrations/dsl_table_with_low_cardinality_creation/1_create_some_table.rb b/spec/fixtures/migrations/dsl_table_with_low_cardinality_creation/1_create_some_table.rb index 10bed2f9..1a9bfff5 100644 --- a/spec/fixtures/migrations/dsl_table_with_low_cardinality_creation/1_create_some_table.rb +++ b/spec/fixtures/migrations/dsl_table_with_low_cardinality_creation/1_create_some_table.rb @@ -7,6 +7,7 @@ def up t.string :col2, low_cardinality: true, null: true t.string :col3, low_cardinality: true, array: true, null: true t.string :col4, low_cardinality: true, map: true, null: true + t.string :col5, low_cardinality: true, map: :array, null: true end end end diff --git a/spec/single/migration_spec.rb b/spec/single/migration_spec.rb index c82fb32c..7d00968e 100644 --- a/spec/single/migration_spec.rb +++ b/spec/single/migration_spec.rb @@ -156,16 +156,18 @@ current_schema = schema(model) - expect(current_schema.keys.count).to eq(4) + expect(current_schema.keys.count).to eq(5) expect(current_schema).to have_key('col1') expect(current_schema).to have_key('col2') expect(current_schema).to have_key('col3') expect(current_schema).to have_key('col4') + expect(current_schema).to have_key('col5') expect(current_schema['col1'].sql_type).to eq('LowCardinality(String)') expect(current_schema['col1'].default).to eq('col') expect(current_schema['col2'].sql_type).to eq('LowCardinality(Nullable(String))') expect(current_schema['col3'].sql_type).to eq('Array(LowCardinality(Nullable(String)))') expect(current_schema['col4'].sql_type).to eq('Map(String, LowCardinality(Nullable(String)))') + expect(current_schema['col5'].sql_type).to eq('Map(String, Array(LowCardinality(Nullable(String))))') end end @@ -176,13 +178,16 @@ current_schema = schema(model) - expect(current_schema.keys.count).to eq(3) + expect(current_schema.keys.count).to eq(4) expect(current_schema).to have_key('fixed_string1') expect(current_schema).to have_key('fixed_string16_array') expect(current_schema).to have_key('fixed_string16_map') + expect(current_schema).to have_key('fixed_string16_map_array') expect(current_schema['fixed_string1'].sql_type).to eq('FixedString(1)') expect(current_schema['fixed_string16_array'].sql_type).to eq('Array(Nullable(FixedString(16)))') expect(current_schema['fixed_string16_map'].sql_type).to eq('Map(String, Nullable(FixedString(16)))') + expect(current_schema['fixed_string16_map_array'].sql_type).to eq('Map(String, Array(Nullable(FixedString(16))))') + end end diff --git a/spec/single/model_spec.rb b/spec/single/model_spec.rb index 6283033e..16fec65d 100644 --- a/spec/single/model_spec.rb +++ b/spec/single/model_spec.rb @@ -401,16 +401,29 @@ class ModelWithoutPrimaryKey < ActiveRecord::Base map_datetime: {a: 1.day.ago, b: Time.now, c: '2022-12-06 15:22:49'}, map_string: {a: 'asdf', b: 'jkl' }, map_int: {a: 1, b: 2}, + map_array_datetime: {a: [1.day.ago], b: [Time.now, '2022-12-06 15:22:49']}, + map_array_string: {a: ['str'], b: ['str1', 'str2']}, + map_array_int: {a: [1], b: [1, 2, 3]}, date: date ) - }.to change { model.count } + }.to change { model.count }.by(1) + record = model.first - expect(record.map_datetime.is_a?(Hash)).to be_truthy - expect(record.map_datetime['a'].is_a?(DateTime)).to be_truthy - expect(record.map_string['a'].is_a?(String)).to be_truthy + expect(record.map_datetime).to be_a Hash + expect(record.map_string).to be_a Hash + expect(record.map_int).to be_a Hash + expect(record.map_array_datetime).to be_a Hash + expect(record.map_array_string).to be_a Hash + expect(record.map_array_int).to be_a Hash + + expect(record.map_datetime['a']).to be_a DateTime + expect(record.map_string['a']).to be_a String expect(record.map_string).to eq({'a' => 'asdf', 'b' => 'jkl'}) - expect(record.map_int.is_a?(Hash)).to be_truthy expect(record.map_int).to eq({'a' => 1, 'b' => 2}) + + expect(record.map_array_datetime['b']).to be_a Array + expect(record.map_array_string['b']).to be_a Array + expect(record.map_array_int['b']).to be_a Array end it 'create with insert all' do @@ -419,21 +432,28 @@ class ModelWithoutPrimaryKey < ActiveRecord::Base map_datetime: {a: 1.day.ago, b: Time.now, c: '2022-12-06 15:22:49'}, map_string: {a: 'asdf', b: 'jkl' }, map_int: {a: 1, b: 2}, + map_array_datetime: {a: [1.day.ago], b: [Time.now, '2022-12-06 15:22:49']}, + map_array_string: {a: ['str'], b: ['str1', 'str2']}, + map_array_int: {a: [1], b: [1, 2, 3]}, date: date }]) - }.to change { model.count } + }.to change { model.count }.by(1) end it 'get record' do - model.connection.insert("INSERT INTO #{model.table_name} (id, map_datetime, date) VALUES (1, {'a': '2022-12-05 15:22:49', 'b': '2022-12-06 15:22:49'}, '2022-12-06')") + model.connection.insert("INSERT INTO #{model.table_name} (id, map_datetime, map_array_datetime, date) VALUES (1, {'a': '2022-12-05 15:22:49', 'b': '2024-01-01 12:00:08'}, {'c': ['2022-12-05 15:22:49','2024-01-01 12:00:08']}, '2022-12-06')") expect(model.count).to eq(1) record = model.first expect(record.date.is_a?(Date)).to be_truthy expect(record.date).to eq(Date.parse('2022-12-06')) - expect(record.map_datetime.is_a?(Hash)).to be_truthy + expect(record.map_datetime).to be_a Hash expect(record.map_datetime['a'].is_a?(DateTime)).to be_truthy expect(record.map_datetime['a']).to eq(DateTime.parse('2022-12-05 15:22:49')) - expect(record.map_datetime['b']).to eq(DateTime.parse('2022-12-06 15:22:49')) + expect(record.map_datetime['b']).to eq(DateTime.parse('2024-01-01 12:00:08')) + expect(record.map_array_datetime).to be_a Hash + expect(record.map_array_datetime['c']).to be_a Array + expect(record.map_array_datetime['c'][0]).to eq(DateTime.parse('2022-12-05 15:22:49')) + expect(record.map_array_datetime['c'][1]).to eq(DateTime.parse('2024-01-01 12:00:08')) end end end