simple-sql 0.2.8 → 0.2.9
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/simple/sql/duplicate.rb +74 -26
- data/lib/simple/sql/version.rb +1 -1
- data/spec/simple/sql_duplicate_spec.rb +5 -5
- data/spec/simple/sql_duplicate_unique_spec.rb +37 -0
- data/spec/simple/sql_insert_spec.rb +0 -5
- data/spec/support/001_database.rb +9 -0
- data/spec/support/factories/user_factory.rb +8 -2
- data/spec/support/model/user.rb +3 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d43876c5b3fa11a6a4a3bc08bae85bc6b1c6763e
|
4
|
+
data.tar.gz: 93d58890035c02fe21e0c6016e7efd9344070a36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7ce6284971ed8827b13aac443c0874e20191a60221b8dbb2745a86a276299a2964451a24623e9ed581dc1e64c88ff1e22dd40f5db18b2146d3dcfbf54f419ce
|
7
|
+
data.tar.gz: d2f5fa8ba870f10a77a8928ec5c9b5be6b11fa55a0e0859040e35b8f276c5dc4341e86223576247ed55df7122ccf2ccb95e9dbad805e23bc1c6b9b07ff4b02b7
|
data/Gemfile.lock
CHANGED
data/lib/simple/sql/duplicate.rb
CHANGED
@@ -1,41 +1,89 @@
|
|
1
|
-
# rubocop:disable
|
2
|
-
# rubocop:disable Metrics/AbcSize
|
1
|
+
# rubocop:disable Style/StructInheritance
|
3
2
|
|
4
3
|
module Simple
|
5
4
|
module SQL
|
6
|
-
|
5
|
+
class Fragment < Struct.new(:to_sql)
|
6
|
+
end
|
7
|
+
|
8
|
+
def fragment(str)
|
9
|
+
Fragment.new(str)
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Creates duplicates of record in a table.
|
14
|
+
#
|
15
|
+
# This method handles timestamp columns (these will be set to the current
|
16
|
+
# time) and primary keys (will be set to NULL.) You can pass in overrides
|
17
|
+
# as a third argument for specific columns.
|
18
|
+
#
|
19
|
+
# Parameters:
|
20
|
+
#
|
21
|
+
# - ids: (Integer, Array<Integer>) primary key ids
|
22
|
+
# - overrides: Hash[column_names => SQL::Fragment]
|
23
|
+
#
|
24
|
+
def duplicate(table, ids, overrides = {})
|
7
25
|
ids = Array(ids)
|
8
26
|
return [] if ids.empty?
|
9
27
|
|
10
|
-
|
11
|
-
|
28
|
+
Duplicator.new(table, overrides).call(ids)
|
29
|
+
end
|
30
|
+
|
31
|
+
class Duplicator
|
32
|
+
attr_reader :table_name, :custom_overrides
|
33
|
+
|
34
|
+
def initialize(table_name, overrides)
|
35
|
+
@table_name = table_name
|
36
|
+
@custom_overrides = validated_overrides(overrides)
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(ids)
|
40
|
+
Simple::SQL.all query, ids
|
41
|
+
rescue PG::UndefinedColumn => e
|
42
|
+
raise ArgumentError, e.message
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# This stringify all keys of the overrides hash, and verifies that
|
48
|
+
# all values in there are SQL::Fragments
|
49
|
+
def validated_overrides(overrides)
|
50
|
+
overrides.inject({}) do |hsh, (key, value)|
|
51
|
+
raise ArgumentError, "Unknown value #{value.inspect}" unless value.is_a?(Fragment)
|
52
|
+
hsh.update key.to_s => value.to_sql
|
53
|
+
end
|
54
|
+
end
|
12
55
|
|
13
|
-
|
14
|
-
|
15
|
-
|
56
|
+
def timestamp_overrides
|
57
|
+
Reflection.timestamp_columns(table_name).inject({}) do |hsh, column|
|
58
|
+
hsh.update column => "now() AS #{column}"
|
59
|
+
end
|
60
|
+
end
|
16
61
|
|
17
|
-
|
18
|
-
|
19
|
-
|
62
|
+
def copy_columns
|
63
|
+
(Reflection.columns(table_name) - Reflection.primary_key_columns(table_name)).inject({}) do |hsh, column|
|
64
|
+
hsh.update column => column
|
65
|
+
end
|
66
|
+
end
|
20
67
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
68
|
+
# build SQL query
|
69
|
+
def query
|
70
|
+
sources = {}
|
24
71
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# with.
|
29
|
-
columns_to_dupe -= except.map(&:to_s)
|
72
|
+
sources.update copy_columns
|
73
|
+
sources.update timestamp_overrides
|
74
|
+
sources.update custom_overrides
|
30
75
|
|
31
|
-
|
32
|
-
|
33
|
-
|
76
|
+
# convert into an Array, to make sure that keys and values aka firsts
|
77
|
+
# and lasts are always in the correct order.
|
78
|
+
sources = sources.to_a
|
34
79
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
80
|
+
<<~SQL
|
81
|
+
INSERT INTO #{table_name}(#{sources.map(&:first).join(', ')})
|
82
|
+
SELECT #{sources.map(&:last).join(', ')}
|
83
|
+
FROM #{table_name}
|
84
|
+
WHERE id = ANY($1) RETURNING id
|
85
|
+
SQL
|
86
|
+
end
|
39
87
|
end
|
40
88
|
end
|
41
89
|
end
|
data/lib/simple/sql/version.rb
CHANGED
@@ -10,24 +10,24 @@ describe "Simple::SQL.duplicate" do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it "does not fail on a non-existing user" do
|
13
|
-
dupe_ids = SQL.duplicate "users",
|
13
|
+
dupe_ids = SQL.duplicate "users", -1
|
14
14
|
|
15
|
-
expect(dupe_ids.length).to eq(
|
16
|
-
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(
|
15
|
+
expect(dupe_ids.length).to eq(0)
|
16
|
+
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(USER_COUNT)
|
17
17
|
end
|
18
18
|
|
19
19
|
it "duplicates a single user" do
|
20
20
|
dupe_ids = SQL.duplicate "users", source_ids.first
|
21
21
|
|
22
22
|
expect(dupe_ids.length).to eq(1)
|
23
|
-
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(
|
23
|
+
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(1 + USER_COUNT)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "duplicates many users" do
|
27
27
|
dupe_ids = SQL.duplicate "users", (source_ids + [ -10 ])
|
28
28
|
|
29
29
|
expect(dupe_ids.length).to eq(2)
|
30
|
-
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(
|
30
|
+
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(2 + USER_COUNT)
|
31
31
|
end
|
32
32
|
|
33
33
|
it "updates the timestamp columns" do
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Simple::SQL.duplicate/unique indices" do
|
4
|
+
let!(:unique_users) { 1.upto(USER_COUNT).map { create(:unique_user) } }
|
5
|
+
|
6
|
+
let!(:source_ids) { SQL.all("SELECT id FROM unique_users") }
|
7
|
+
|
8
|
+
it "cannot duplicate unique_user" do
|
9
|
+
expect {
|
10
|
+
SQL.duplicate "unique_users", source_ids
|
11
|
+
}.to raise_error(PG::UniqueViolation)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "raises an ArgumentError when called with unknown columns" do
|
15
|
+
expect {
|
16
|
+
SQL.duplicate "unique_users", source_ids, foo: SQL.fragment("baz")
|
17
|
+
}.to raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises an ArgumentError when called with invalid overrides" do
|
21
|
+
expect {
|
22
|
+
SQL.duplicate "unique_users", source_ids, first_name: "I am invalid"
|
23
|
+
}.to raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "duplicates unique_users" do
|
27
|
+
overrides = {
|
28
|
+
first_name: SQL.fragment("first_name || '.' || id"),
|
29
|
+
last_name: SQL.fragment("last_name || '.' || id")
|
30
|
+
}
|
31
|
+
|
32
|
+
dupe_ids = SQL.duplicate "unique_users", source_ids, overrides
|
33
|
+
|
34
|
+
expect(dupe_ids.length).to eq(2)
|
35
|
+
expect(SQL.ask("SELECT COUNT(*) FROM unique_users")).to eq(4)
|
36
|
+
end
|
37
|
+
end
|
@@ -15,11 +15,6 @@ describe "Simple::SQL.insert" do
|
|
15
15
|
expect(user.first_name).to eq("foo")
|
16
16
|
expect(user.last_name).to eq("bar")
|
17
17
|
expect(user.created_at).to be_a(Time)
|
18
|
-
|
19
|
-
#
|
20
|
-
# r = SQL.record("SELECT COUNT(*) AS count FROM users")
|
21
|
-
# r = SQL.record("SELECT COUNT(*) AS count FROM users")
|
22
|
-
# expect(r).to eq({count: 2})
|
23
18
|
end
|
24
19
|
end
|
25
20
|
|
@@ -35,4 +35,13 @@ ActiveRecord::Schema.define do
|
|
35
35
|
|
36
36
|
t.timestamps null: true
|
37
37
|
end
|
38
|
+
|
39
|
+
create_table :unique_users, force: true do |t|
|
40
|
+
t.string :first_name
|
41
|
+
t.string :last_name
|
42
|
+
end
|
43
|
+
|
44
|
+
execute <<-SQL
|
45
|
+
CREATE UNIQUE INDEX unique_users_ix1 ON unique_users(first_name, last_name)
|
46
|
+
SQL
|
38
47
|
end
|
@@ -1,8 +1,14 @@
|
|
1
1
|
FactoryGirl.define do
|
2
2
|
factory :user do
|
3
3
|
role_id 123
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
sequence :first_name do |n| "First #{n}" end
|
6
|
+
sequence :last_name do |n| "Last #{n}" end
|
6
7
|
access_level "viewable"
|
7
8
|
end
|
9
|
+
|
10
|
+
factory :unique_user do
|
11
|
+
sequence :first_name do |n| "First #{n}" end
|
12
|
+
sequence :last_name do |n| "Last #{n}" end
|
13
|
+
end
|
8
14
|
end
|
data/spec/support/model/user.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radiospiel
|
@@ -198,6 +198,7 @@ files:
|
|
198
198
|
- spec/simple/sql_config_spec.rb
|
199
199
|
- spec/simple/sql_conversion_spec.rb
|
200
200
|
- spec/simple/sql_duplicate_spec.rb
|
201
|
+
- spec/simple/sql_duplicate_unique_spec.rb
|
201
202
|
- spec/simple/sql_insert_spec.rb
|
202
203
|
- spec/simple/sql_record_spec.rb
|
203
204
|
- spec/simple/sql_reflection_spec.rb
|
@@ -238,6 +239,7 @@ test_files:
|
|
238
239
|
- spec/simple/sql_config_spec.rb
|
239
240
|
- spec/simple/sql_conversion_spec.rb
|
240
241
|
- spec/simple/sql_duplicate_spec.rb
|
242
|
+
- spec/simple/sql_duplicate_unique_spec.rb
|
241
243
|
- spec/simple/sql_insert_spec.rb
|
242
244
|
- spec/simple/sql_record_spec.rb
|
243
245
|
- spec/simple/sql_reflection_spec.rb
|