insert_select 0.1.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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