diff --git a/lib/polo/adapters/mysql.rb b/lib/polo/adapters/mysql.rb index 11a3486..38a9333 100644 --- a/lib/polo/adapters/mysql.rb +++ b/lib/polo/adapters/mysql.rb @@ -9,7 +9,8 @@ def on_duplicate_key_update(inserts, records) "`#{key}` = VALUES(`#{key}`)" end - on_dup_syntax = "ON DUPLICATE KEY UPDATE #{values_syntax.join(', ')}" + on_dup_syntax = "ON DUPLICATE KEY UPDATE #{values_syntax.join(', ')};" + insert.gsub!(/;$/, '') "#{insert} #{on_dup_syntax}" end @@ -22,4 +23,4 @@ def ignore_transform(inserts, records) end end end -end \ No newline at end of file +end diff --git a/lib/polo/adapters/postgres.rb b/lib/polo/adapters/postgres.rb index 6bd1c37..5a2281e 100644 --- a/lib/polo/adapters/postgres.rb +++ b/lib/polo/adapters/postgres.rb @@ -28,6 +28,7 @@ def ignore_transform(inserts, records) id = record[:id] table_name = record.class.arel_table.name end + insert.gsub!(/;$/, '') insert = insert.gsub(/VALUES \((.+)\)$/m, 'SELECT \\1') insert << " WHERE NOT EXISTS (SELECT 1 FROM #{table_name} WHERE id=#{id});" end diff --git a/lib/polo/sql_translator.rb b/lib/polo/sql_translator.rb index 9d52866..7960024 100644 --- a/lib/polo/sql_translator.rb +++ b/lib/polo/sql_translator.rb @@ -36,14 +36,24 @@ def records def inserts records.map do |record| - if record.is_a?(Hash) + sql = if record.is_a?(Hash) raw_sql_from_hash(record) else raw_sql_from_record(record) end + + sql.end_with?(";") ? sql : sql + ";" end end + def insertable_columns(record) + record.class.column_names - virtual_columns(record) + end + + def virtual_columns(record) + record.class.columns_hash.select { |k, v| v.try(:virtual?) }.keys + end + private # Internal: Generates an insert SQL statement for a given record @@ -103,7 +113,7 @@ def insert_values(record) module ActiveRecordFivePointZeroOrOne # Based on the codepath used in Rails 5 def raw_sql_from_record(record) - values = record.send(:arel_attributes_with_values_for_create, record.class.column_names) + values = record.send(:arel_attributes_with_values_for_create, insertable_columns(record)) model = record.class substitutes, binds = model.unscoped.substitute_values(values) @@ -120,7 +130,7 @@ def raw_sql_from_record(record) # their set values (for Rails >= 5.2). module ActiveRecordFive def raw_sql_from_record(record) - values = record.send(:attributes_with_values_for_create, record.class.column_names) + values = record.send(:attributes_with_values_for_create, insertable_columns(record)) model = record.class substitutes_and_binds = model.send(:_substitute_values, values) @@ -154,7 +164,7 @@ def raw_sql_from_record(record) # their set values (for Rails 7.0). module ActiveRecordSeven def raw_sql_from_record(record) - values = record.send(:attributes_with_values, record.class.column_names) + values = record.send(:attributes_with_values, insertable_columns(record)) model = record.class substitutes_and_binds = values.transform_keys { |name| model.arel_table[name] } diff --git a/spec/adapters/mysql_spec.rb b/spec/adapters/mysql_spec.rb index 5aed1b0..96c9536 100644 --- a/spec/adapters/mysql_spec.rb +++ b/spec/adapters/mysql_spec.rb @@ -16,7 +16,7 @@ describe '#ignore_transform' do it 'appends the IGNORE command after INSERTs' do - insert_netto = [%q{INSERT IGNORE INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com')}] + insert_netto = [%q{INSERT IGNORE INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com');}] records = translator.records inserts = translator.inserts @@ -29,7 +29,7 @@ describe '#on_duplicate_key_update' do it 'appends ON DUPLICATE KEY UPDATE with all values to the current INSERT statement' do insert_netto = [ - %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com') ON DUPLICATE KEY UPDATE `id` = VALUES(`id`), `name` = VALUES(`name`), `email` = VALUES(`email`)} + %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com') ON DUPLICATE KEY UPDATE `id` = VALUES(`id`), `name` = VALUES(`name`), `email` = VALUES(`email`);} ] inserts = translator.inserts @@ -39,7 +39,7 @@ end it 'works for hash-values instead of ActiveRecord instances' do insert_netto = [ - %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com') ON DUPLICATE KEY UPDATE `id` = VALUES(`id`), `name` = VALUES(`name`), `email` = VALUES(`email`)} + %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com') ON DUPLICATE KEY UPDATE `id` = VALUES(`id`), `name` = VALUES(`name`), `email` = VALUES(`email`);} ] inserts = translator.inserts @@ -48,4 +48,4 @@ expect(translated_sql).to eq(insert_netto) end end -end \ No newline at end of file +end diff --git a/spec/polo_spec.rb b/spec/polo_spec.rb index 3b040fb..e96b57e 100644 --- a/spec/polo_spec.rb +++ b/spec/polo_spec.rb @@ -8,13 +8,13 @@ it 'generates an insert query for the base object' do exp = Polo.explore(AR::Chef, 1) - insert = %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com')} + insert = %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com');} expect(exp).to include(insert) end it 'generates an insert query for the objects with non-standard primary keys' do exp = Polo.explore(AR::Person, 1) - insert = %q{INSERT INTO "people" ("ssn", "name") VALUES (1, 'John Doe')} + insert = %q{INSERT INTO "people" ("ssn", "name") VALUES (1, 'John Doe');} expect(exp).to include(insert) end @@ -25,8 +25,8 @@ serialized_nil = "'null'" end - turkey_insert = %Q{INSERT INTO "recipes" ("id", "title", "num_steps", "chef_id", "metadata") VALUES (1, 'Turkey Sandwich', NULL, 1, #{serialized_nil})} - cheese_burger_insert = %Q{INSERT INTO "recipes" ("id", "title", "num_steps", "chef_id", "metadata") VALUES (2, 'Cheese Burger', NULL, 1, #{serialized_nil})} + turkey_insert = %Q{INSERT INTO "recipes" ("id", "title", "num_steps", "chef_id", "metadata") VALUES (1, 'Turkey Sandwich', NULL, 1, #{serialized_nil});} + cheese_burger_insert = %Q{INSERT INTO "recipes" ("id", "title", "num_steps", "chef_id", "metadata") VALUES (2, 'Cheese Burger', NULL, 1, #{serialized_nil});} inserts = Polo.explore(AR::Chef, 1, [:recipes]) @@ -35,10 +35,10 @@ end it 'generates queries for nested dependencies' do - patty = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (3, 'Patty', '1')} - turkey = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (1, 'Turkey', 'a lot')} - one_cheese = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (2, 'Cheese', '1 slice')} - two_cheeses = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (4, 'Cheese', '2 slices')} + patty = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (3, 'Patty', '1');} + turkey = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (1, 'Turkey', 'a lot');} + one_cheese = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (2, 'Cheese', '1 slice');} + two_cheeses = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (4, 'Cheese', '2 slices');} inserts = Polo.explore(AR::Chef, 1, :recipes => :ingredients) @@ -52,8 +52,8 @@ ar_version = ActiveRecord::VERSION::STRING skip("Not supported on ActiveRecord #{ar_version}") if ar_version < "4.1.0" habtm_inserts = [ - %q{INSERT INTO "recipes_tags" ("recipe_id", "tag_id") VALUES (2, 2)}, - %q{INSERT INTO "tags" ("id", "name") VALUES (2, 'burgers')} + %q{INSERT INTO "recipes_tags" ("recipe_id", "tag_id") VALUES (2, 2);}, + %q{INSERT INTO "tags" ("id", "name") VALUES (2, 'burgers');} ] inserts = Polo.explore(AR::Chef, 1, :recipes => :tags) @@ -65,10 +65,10 @@ it 'generates inserts for many to many relationships' do many_to_many_inserts = [ - %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (1, 1, 1)}, - %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (2, 1, 2)}, - %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (3, 2, 3)}, - %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (4, 2, 4)}, + %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (1, 1, 1);}, + %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (2, 1, 2);}, + %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (3, 2, 3);}, + %q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (4, 2, 4);}, ] inserts = Polo.explore(AR::Chef, 1, :recipes => :ingredients) @@ -87,7 +87,7 @@ end exp = Polo.explore(AR::Chef, 1) - insert = /INSERT INTO "chefs" \("id", "name", "email"\) VALUES \(1, 'Netto', (.+)\)/ + insert = /INSERT INTO "chefs" \("id", "name", "email"\) VALUES \(1, 'Netto', (.+)\);/ scrambled_email = insert.match(exp.first)[1] expect(scrambled_email).to_not eq('nettofarah@gmail.com') @@ -101,7 +101,7 @@ inserts = Polo.explore(AR::Chef, 1) - expect(inserts).to eq [ %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'changeme')} ] + expect(inserts).to eq [ %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'changeme');} ] end it 'only scrambles instances with the obfuscate field defined' do @@ -137,7 +137,7 @@ end exp = Polo.explore(AR::Chef, 1) - insert = /INSERT IGNORE INTO "chefs" \("id", "name", "email"\) VALUES \(1, 'Netto', (.+)\)/ + insert = /INSERT IGNORE INTO "chefs" \("id", "name", "email"\) VALUES \(1, 'Netto', (.+)\);/ expect(insert).to match(exp.first) end end diff --git a/spec/sql_translator_spec.rb b/spec/sql_translator_spec.rb index dc9b5f8..b63f4b5 100644 --- a/spec/sql_translator_spec.rb +++ b/spec/sql_translator_spec.rb @@ -11,7 +11,7 @@ end it 'translates records to inserts' do - insert_netto = [%q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com')}] + insert_netto = [%q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com');}] netto_to_sql = Polo::SqlTranslator.new(netto).to_sql expect(netto_to_sql).to eq(insert_netto) end