insert_select 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ed398397217cbd7d7a636948b89a3cfb32bbfa6c9288619e249e014f895febc
4
- data.tar.gz: 015b72e911c1183215858260e3aa96dc6ec47000f2221ec7c7147929deb94167
3
+ metadata.gz: 5f35ad1a5ba6691ef951e0b312f72a88f9cf1f63a3c78d365ba609ce328cffb4
4
+ data.tar.gz: 8d9bdf2e2096814a94d483e156e99e315fbac5f82ae5bc06df57715afd9f36ec
5
5
  SHA512:
6
- metadata.gz: 9b5ec0d676bb246917b348e656bcb20b89ea4fdb509ae834dc1f32704f2c8faa97a8ce394b6629deb26001f06cdc59550c01c78f49febbb94e936cc1ec88d9a1
7
- data.tar.gz: 5179a2af5b2b62670f42f1b1fec76f9c38cfc2c9d025e82de60b4d5f6e3a16df27096992896533abf25759b8423dc68afce0939e09b1486392022e74168b50fe
6
+ metadata.gz: ada0f0f8289682ac790410639eaeca097cdf95ae3d1345b4ccadc0e3ec0133cef30b4c966223d29f6212047b5ab78f59d14f3032b6bf98ac9e8c0d6abb2760d6
7
+ data.tar.gz: 60f34464bab7946821a59620bcee6250407e674e54320a91f0f3310472ac81832159a7ecd684c1f13845f43677695b35de90102819e00e307fee03876b8e8eef
data/Gemfile CHANGED
@@ -9,3 +9,5 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
  gem "sqlite3"
12
+ gem "mysql2"
13
+ gem "pg"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- insert_select (0.1.0)
4
+ insert_select (1.0.1)
5
5
  activerecord
6
6
 
7
7
  GEM
@@ -22,6 +22,8 @@ GEM
22
22
  i18n (1.14.0)
23
23
  concurrent-ruby (~> 1.0)
24
24
  minitest (5.18.0)
25
+ mysql2 (0.5.5)
26
+ pg (1.5.3)
25
27
  rake (13.0.6)
26
28
  rspec (3.12.0)
27
29
  rspec-core (~> 3.12.0)
@@ -48,6 +50,8 @@ PLATFORMS
48
50
 
49
51
  DEPENDENCIES
50
52
  insert_select!
53
+ mysql2
54
+ pg
51
55
  rake (~> 13.0)
52
56
  rspec (~> 3.0)
53
57
  sqlite3
data/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
  [![Ruby](https://github.com/a5-stable/insert_select/actions/workflows/ruby.yml/badge.svg)](https://github.com/a5-stable/insert_select/actions/workflows/ruby.yml)
3
3
  ![Code Climate](https://codeclimate.com/github/a5-stable/insert_select.png)
4
4
 
5
- This is a custom gem that extends ActiveRecord to enable the expression of SQL `INSERT INTO ... SELECT ...` queries in a more convenient way. It allows you to copy data from one table to another based on specified conditions using a simple and expressive syntax.
5
+ This is a custom gem that extends ActiveRecord to enable the expression of SQL `INSERT INTO ... SELECT ...` queries in a more convenient way.
6
+ It allows you to copy data from one table to another based on specified conditions using a simple and expressive syntax.
6
7
 
7
8
  SQL example of `INSERT INTO ... SELECT ...`:
8
9
  ```
@@ -33,7 +34,7 @@ gem install insert_select
33
34
 
34
35
  ## Usage
35
36
 
36
- ### Copy all data from OldUser to NewUser
37
+ #### Copy all data from OldUser to NewUser
37
38
 
38
39
  ```ruby
39
40
  NewUser.insert_select_from(OldUser)
@@ -41,7 +42,7 @@ NewUser.insert_select_from(OldUser)
41
42
  #=> INSERT INTO "new_users" SELECT "old_users".* FROM "old_users"
42
43
  ```
43
44
 
44
- ### Filter the columns to be copied
45
+ #### Filter the columns to be copied
45
46
 
46
47
  ```ruby
47
48
  NewUser.insert_select_from(OldUser.select(:name))
@@ -49,7 +50,7 @@ NewUser.insert_select_from(OldUser.select(:name))
49
50
  #=> INSERT INTO "new_users" ("name") SELECT "old_users"."name" FROM "old_users"
50
51
  ```
51
52
 
52
- ### Copy data between different column names
53
+ #### Copy data between different column names
53
54
 
54
55
  You can specify column name mappings between tables.
55
56
  ```ruby
@@ -58,28 +59,43 @@ AnotherUser.insert_select_from(OldUser, mapping: { old_name: :another_name })
58
59
  #=> INSERT INTO "another_users" ("another_name") SELECT "old_users"."name" FROM "old_users"
59
60
  ```
60
61
 
61
- ### Set a constant value
62
+ #### Set a constant value
62
63
  ```ruby
63
64
  AnotherUser.create_with(another_constant_column: "20").insert_select_from(OldUser, mapping: { old_name: :another_name })
64
65
 
65
66
  #=> INSERT INTO "another_users" ("another_name", "another_constant_column") SELECT "old_users"."name", "20" FROM "old_users"
66
67
  ```
67
68
 
68
- ### Use a WHERE clause to filter the data
69
+ #### Use a WHERE clause to filter the data
69
70
  ```ruby
70
71
  NewUser.insert_select_from(OldUser.where("age > ?", 20))
71
72
 
72
73
  #=> INSERT INTO "new_users" SELECT "old_users".*, FROM "old_users" WHERE ("age" > 20)
73
74
  ```
74
75
 
75
- ### Use the RETURNING clause (only for PostgreSQL connection)
76
+ #### Use the RETURNING clause (only for PostgreSQL connection)
76
77
  ```ruby
77
78
  NewUser.insert_select_from(OldUser, returning: [:id])
78
79
 
79
80
  #=> INSERT INTO "new_users" SELECT "old_users".* FROM "old_users" RETURNING "id"
80
81
  ```
82
+ #### Bang method
83
+ In default, any duplicated records are skipped in the `insert_select_from` method.
84
+ If you want to raise an error when a duplicated record is found, use the bang method.
85
+
86
+ Assume that `id` is a primary key.
87
+ This example does not raise an `ActiveRecord::RecordNotUnique` exception and no record is inserted.
88
+ ```ruby
89
+ User.insert_select_from(User)
90
+ ```
91
+
92
+ This example raises an `ActiveRecord::RecordNotUnique` exception.
93
+ ```ruby
94
+ User.insert_select_from!(User)
95
+
96
+ #=> ActiveRecord::RecordNotUnique (SQLite3::ConstraintException: UNIQUE constraint failed: users.id)
97
+ ```
81
98
 
82
- Other options, which are enabled in [`insert_all`](https://www.rubydoc.info/github/rails/rails/ActiveRecord%2FPersistence%2FClassMethods:insert_all) should be also supported, but are not yet implemented.
83
99
 
84
100
  ## Development
85
101
 
data/Rakefile CHANGED
@@ -5,4 +5,15 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
+ namespace :spec do
9
+ ["postgresql", "mysql2", "sqlite3"].each do |adapter|
10
+ RSpec::Core::RakeTask.new(adapter) do |t|
11
+ ENV["ADAPTER_NAME"] = adapter
12
+ ENV["DATABASE_NAME"] = adapter == "sqlite3" ? ":memory:" : "insert_select_test"
13
+ t.pattern = FileList["spec/*_spec.rb"]
14
+ end
15
+ end
16
+ end
17
+
18
+ task spec: ["spec:postgresql", "spec:mysql2", "spec:sqlite3"]
8
19
  task default: :spec
@@ -34,7 +34,11 @@ module InsertSelect
34
34
  # @return [ActiveRecord::Result] The result of the insert select operation.
35
35
  #
36
36
  def insert_select_from(relation, mapping: {}, returning: nil)
37
- InsertSelect::ActiveRecord::InsertSelectFrom.new(self, relation, mapping: mapping, returning: returning).execute
37
+ InsertSelect::ActiveRecord::InsertSelectFrom.new(self, relation, mapping: mapping, on_duplicate: :skip, returning: returning).execute
38
+ end
39
+
40
+ def insert_select_from!(relation, mapping: {}, returning: nil)
41
+ InsertSelect::ActiveRecord::InsertSelectFrom.new(self, relation, mapping: mapping, on_duplicate: :raise, returning: returning).execute
38
42
  end
39
43
 
40
44
  def except(*columns)
@@ -44,20 +48,21 @@ module InsertSelect
44
48
  end
45
49
 
46
50
  class InsertSelectFrom
47
- attr_reader :model, :connection, :relation, :adapter, :mapping, :returning
51
+ attr_reader :model, :connection, :relation, :adapter, :mapping, :returning, :on_duplicate
48
52
 
49
- def initialize(model, relation, mapping:, returning: nil)
53
+ def initialize(model, relation, mapping:, on_duplicate:, returning: nil)
50
54
  @model = model
51
55
  @connection = model.connection
52
56
  @relation = relation
53
57
  @adapter = find_adapter(connection)
54
58
  @mapping = mapping
55
59
  @returning = returning
60
+ @on_duplicate = on_duplicate
56
61
  end
57
62
 
58
63
  def execute
59
64
  sql = model.sanitize_sql_array([to_sql, *builder.constant_values])
60
- connection.exec_insert_all(sql, "")
65
+ connection.exec_insert_all(sql, "Bulk Insert")
61
66
  end
62
67
 
63
68
  def to_sql
@@ -7,7 +7,13 @@ module InsertSelect
7
7
  end
8
8
 
9
9
  def build_sql(builder)
10
- super
10
+ stmt = super
11
+
12
+ if builder.on_duplicate == :skip
13
+ stmt << " ON DUPLICATE KEY UPDATE `id`= VALUES(`id`) "
14
+ end
15
+
16
+ stmt
11
17
  end
12
18
  end
13
19
  end
@@ -7,10 +7,12 @@ module InsertSelect
7
7
  end
8
8
 
9
9
  def build_sql(builder)
10
- sql = super
11
- sql += " RETURNING #{builder.returning}" if builder.returning
10
+ stmt = super
12
11
 
13
- sql
12
+ stmt << " ON CONFLICT DO NOTHING" if builder.on_duplicate == :skip
13
+ stmt << " RETURNING #{builder.returning}" if builder.returning?
14
+
15
+ stmt
14
16
  end
15
17
  end
16
18
  end
@@ -7,7 +7,18 @@ module InsertSelect
7
7
  end
8
8
 
9
9
  def build_sql(builder)
10
- super
10
+ # have to be done before we call super, because super will make relation immutable
11
+ if builder.on_duplicate == :skip
12
+ builder.relation.where!("TRUE") if builder.relation.where_clause.blank?
13
+ end
14
+
15
+ stmt = super
16
+
17
+ if builder.on_duplicate == :skip
18
+ stmt << " ON CONFLICT DO NOTHING"
19
+ end
20
+
21
+ stmt
11
22
  end
12
23
  end
13
24
  end
@@ -1,7 +1,7 @@
1
1
  module InsertSelect
2
2
  module ActiveRecord
3
3
  class Builder
4
- attr_reader :relation, :mapping, :model, :returning, :insert_select_from, :connection
4
+ attr_reader :relation, :mapping, :model,:insert_select_from, :connection, :returning, :on_duplicate
5
5
 
6
6
  def initialize(insert_select_from)
7
7
  @insert_select_from = insert_select_from
@@ -10,6 +10,7 @@ module InsertSelect
10
10
  @mapping = insert_select_from.mapping || {}
11
11
  @model = insert_select_from.model
12
12
  @returning = insert_select_from.returning
13
+ @on_duplicate = insert_select_from.on_duplicate
13
14
  end
14
15
 
15
16
  def mapper
@@ -58,7 +59,10 @@ module InsertSelect
58
59
  end
59
60
 
60
61
  def constant_values
61
- constant_mapping.values
62
+ constant_mapping.map do |k, v|
63
+ type = model.type_for_attribute(k.to_s)
64
+ type.serialize(v)
65
+ end
62
66
  end
63
67
 
64
68
  def reselect_relation!
@@ -90,6 +94,10 @@ module InsertSelect
90
94
  end
91
95
  end
92
96
 
97
+ def returning?
98
+ @returning.present?
99
+ end
100
+
93
101
  private
94
102
 
95
103
  def remove_select_values!(column_name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InsertSelect
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: insert_select
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - a5-stable
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-22 00:00:00.000000000 Z
11
+ date: 2023-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord