simple-sql 0.2.7 → 0.2.8
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.rb +1 -2
- data/lib/simple/sql/duplicate.rb +41 -0
- data/lib/simple/sql/insert.rb +1 -12
- data/lib/simple/sql/reflection.rb +45 -14
- data/lib/simple/sql/version.rb +1 -1
- data/spec/simple/sql_duplicate_spec.rb +44 -0
- data/spec/simple/sql_insert_spec.rb +0 -4
- data/spec/simple/sql_record_spec.rb +0 -4
- data/spec/simple/sql_reflection_spec.rb +4 -12
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00ca6e591d2e1aecbf57de4b27c3693e559c6ce5
|
4
|
+
data.tar.gz: c61d3dfa4eeede7e8e1c5a5c59cac3f6c788e197
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d9fe86fe3a97443857b51c33294512be9f9a9b555b4783b4807ad85fd2c51a948b4cae4ba96dfb941ae286fa6870cabd777c161517d4c7c14a00ff6c234846a
|
7
|
+
data.tar.gz: 94b1a01d68fd2b2ddc6ec5aceb9e7dda4dce2c86aed7a3bae2f310278004d221652ddd88ada42f583d59d236bfddcfa511f93a6dafa753b2b521b8b2ad802bd0
|
data/Gemfile.lock
CHANGED
data/lib/simple/sql.rb
CHANGED
@@ -9,14 +9,13 @@ require_relative "sql/logging.rb"
|
|
9
9
|
require_relative "sql/connection.rb"
|
10
10
|
require_relative "sql/reflection.rb"
|
11
11
|
require_relative "sql/insert.rb"
|
12
|
+
require_relative "sql/duplicate.rb"
|
12
13
|
|
13
14
|
module Simple
|
14
15
|
# The Simple::SQL module
|
15
16
|
module SQL
|
16
17
|
extend self
|
17
18
|
|
18
|
-
attr_accessor :logger
|
19
|
-
|
20
19
|
def logger
|
21
20
|
@logger ||= default_logger
|
22
21
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# rubocop:disable Metrics/MethodLength
|
2
|
+
# rubocop:disable Metrics/AbcSize
|
3
|
+
|
4
|
+
module Simple
|
5
|
+
module SQL
|
6
|
+
def duplicate(table, ids, except: [])
|
7
|
+
ids = Array(ids)
|
8
|
+
return [] if ids.empty?
|
9
|
+
|
10
|
+
timestamp_columns = Reflection.timestamp_columns(table)
|
11
|
+
primary_key_columns = Reflection.primary_key_columns(table)
|
12
|
+
|
13
|
+
# duplicate all columns in the table that need to be SELECTed.
|
14
|
+
#
|
15
|
+
columns_to_dupe = Reflection.columns(table)
|
16
|
+
|
17
|
+
# Primary keys will not be selected from the table, they should be set
|
18
|
+
# automatically by the database, via a DEFAULT role on the column.
|
19
|
+
columns_to_dupe -= primary_key_columns
|
20
|
+
|
21
|
+
# timestamp_columns will not be selected from the table, they will be
|
22
|
+
# set to now() explicitely.
|
23
|
+
columns_to_dupe -= timestamp_columns
|
24
|
+
|
25
|
+
# If some other columns must be excluded they have to be added in the
|
26
|
+
# except: keyword argument. This is helpful for UNIQUE columns, but
|
27
|
+
# a column which is supposed to be UNIQUE and NOT NULL can not be dealt
|
28
|
+
# with.
|
29
|
+
columns_to_dupe -= except.map(&:to_s)
|
30
|
+
|
31
|
+
# build query
|
32
|
+
select_columns = columns_to_dupe + timestamp_columns
|
33
|
+
select_values = columns_to_dupe + timestamp_columns.map { |col| "now() AS #{col}" }
|
34
|
+
|
35
|
+
Simple::SQL.all <<~SQL, ids
|
36
|
+
INSERT INTO #{table}(#{select_columns.join(', ')})
|
37
|
+
SELECT #{select_values.join(', ')} FROM #{table} WHERE id = ANY($1) RETURNING id
|
38
|
+
SQL
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/simple/sql/insert.rb
CHANGED
@@ -42,7 +42,7 @@ module Simple
|
|
42
42
|
cols += columns
|
43
43
|
vals += columns.each_with_index.map { |_, idx| "$#{idx + 1}" }
|
44
44
|
|
45
|
-
timestamp_columns =
|
45
|
+
timestamp_columns = SQL::Reflection.timestamp_columns(table_name) - columns.map(&:to_s)
|
46
46
|
|
47
47
|
cols += timestamp_columns
|
48
48
|
vals += timestamp_columns.map { "now()" }
|
@@ -50,17 +50,6 @@ module Simple
|
|
50
50
|
@sql = "INSERT INTO #{table_name} (#{cols.join(',')}) VALUES(#{vals.join(',')}) RETURNING id"
|
51
51
|
end
|
52
52
|
|
53
|
-
# timestamp_columns are columns that will be set to the current time when
|
54
|
-
# inserting a record. This includes:
|
55
|
-
#
|
56
|
-
# - inserted_at (for Ecto)
|
57
|
-
# - created_at (for ActiveRecord)
|
58
|
-
# - updated_at (for Ecto and ActiveRecord)
|
59
|
-
def timestamp_columns_in_table(table_name)
|
60
|
-
columns_for_table = SQL::Reflection.columns(table_name).keys
|
61
|
-
columns_for_table & %w(inserted_at created_at updated_at)
|
62
|
-
end
|
63
|
-
|
64
53
|
def insert(records:)
|
65
54
|
SQL.transaction do
|
66
55
|
records.map do |record|
|
@@ -9,23 +9,55 @@ module Simple
|
|
9
9
|
delegate [:ask, :all, :records, :record] => ::Simple::SQL
|
10
10
|
|
11
11
|
def tables(schema: "public")
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
table_info(schema: schema).keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def primary_key_columns(table_name)
|
16
|
+
all <<~SQL, table_name
|
17
|
+
SELECT pg_attribute.attname
|
18
|
+
FROM pg_index
|
19
|
+
JOIN pg_attribute ON pg_attribute.attrelid = pg_index.indrelid AND pg_attribute.attnum = ANY(pg_index.indkey)
|
20
|
+
WHERE pg_index.indrelid = $1::regclass
|
21
|
+
AND pg_index.indisprimary;
|
22
|
+
SQL
|
23
|
+
end
|
24
|
+
|
25
|
+
TIMESTAMP_COLUMN_NAMES = %w(inserted_at created_at updated_at)
|
26
|
+
|
27
|
+
# timestamp_columns are columns that will be set automatically after
|
28
|
+
# inserting or updating a record. This includes:
|
29
|
+
#
|
30
|
+
# - inserted_at (for Ecto)
|
31
|
+
# - created_at (for ActiveRecord)
|
32
|
+
# - updated_at (for Ecto and ActiveRecord)
|
33
|
+
def timestamp_columns(table_name)
|
34
|
+
columns(table_name) & TIMESTAMP_COLUMN_NAMES
|
35
|
+
end
|
36
|
+
|
37
|
+
def columns(table_name)
|
38
|
+
column_info(table_name).keys
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def table_info(schema: "public")
|
44
|
+
columns = if schema == "public"
|
45
|
+
"table_name AS name, *"
|
46
|
+
else
|
47
|
+
"table_schema || '.' || table_name AS name, *"
|
48
|
+
end
|
49
|
+
|
50
|
+
recs = records <<~SQL, schema
|
51
|
+
SELECT #{columns}
|
20
52
|
FROM information_schema.tables
|
21
53
|
WHERE table_schema=$1
|
22
54
|
SQL
|
23
|
-
records_by_attr(
|
55
|
+
records_by_attr(recs, :name)
|
24
56
|
end
|
25
57
|
|
26
|
-
def
|
58
|
+
def column_info(table_name)
|
27
59
|
schema, table_name = parse_table_name(table_name)
|
28
|
-
|
60
|
+
recs = records <<~SQL, schema, table_name
|
29
61
|
SELECT
|
30
62
|
column_name AS name,
|
31
63
|
*
|
@@ -33,11 +65,9 @@ module Simple
|
|
33
65
|
WHERE table_schema=$1 AND table_name=$2
|
34
66
|
SQL
|
35
67
|
|
36
|
-
records_by_attr(
|
68
|
+
records_by_attr(recs, :column_name)
|
37
69
|
end
|
38
70
|
|
39
|
-
private
|
40
|
-
|
41
71
|
def parse_table_name(table_name)
|
42
72
|
p1, p2 = table_name.split(".", 2)
|
43
73
|
if p2
|
@@ -49,6 +79,7 @@ module Simple
|
|
49
79
|
|
50
80
|
def records_by_attr(records, attr)
|
51
81
|
records.inject({}) do |hsh, record|
|
82
|
+
record.reject! { |_k, v| v.nil? }
|
52
83
|
hsh.update record[attr] => OpenStruct.new(record)
|
53
84
|
end
|
54
85
|
end
|
data/lib/simple/sql/version.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Simple::SQL.duplicate" do
|
4
|
+
let!(:users) { 1.upto(USER_COUNT).map { create(:user) } }
|
5
|
+
|
6
|
+
let!(:source_ids) { SQL.all("SELECT id FROM users") }
|
7
|
+
|
8
|
+
before do
|
9
|
+
SQL.ask "UPDATE users SET created_at=created_at - interval '1 hour', updated_at=updated_at - interval '1 hour'"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "does not fail on a non-existing user" do
|
13
|
+
dupe_ids = SQL.duplicate "users", source_ids.first
|
14
|
+
|
15
|
+
expect(dupe_ids.length).to eq(1)
|
16
|
+
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(3)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "duplicates a single user" do
|
20
|
+
dupe_ids = SQL.duplicate "users", source_ids.first
|
21
|
+
|
22
|
+
expect(dupe_ids.length).to eq(1)
|
23
|
+
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(3)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "duplicates many users" do
|
27
|
+
dupe_ids = SQL.duplicate "users", (source_ids + [ -10 ])
|
28
|
+
|
29
|
+
expect(dupe_ids.length).to eq(2)
|
30
|
+
expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(4)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "updates the timestamp columns" do
|
34
|
+
source_id = source_ids.first
|
35
|
+
dupe_ids = SQL.duplicate "users", source_ids.first
|
36
|
+
dupe_id = dupe_ids.first
|
37
|
+
|
38
|
+
source_timestamp = SQL.ask("SELECT updated_at FROM users WHERE id=$1", source_id)
|
39
|
+
dupe_timestamp = SQL.ask("SELECT updated_at FROM users WHERE id=$1", dupe_id)
|
40
|
+
|
41
|
+
expect(dupe_timestamp).to be > source_timestamp
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -3,29 +3,21 @@ require "spec_helper"
|
|
3
3
|
describe "Simple::SQL::Reflection" do
|
4
4
|
describe ".columns" do
|
5
5
|
it "returns the columns of a table in the public schema" do
|
6
|
-
|
7
|
-
expect(r).to be_a(Hash)
|
8
|
-
expect(r["first_name"].name).to eq("first_name")
|
6
|
+
expect(SQL::Reflection.columns("users")).to include("first_name")
|
9
7
|
end
|
10
8
|
|
11
9
|
it "returns the columns of a table in a non-'public' schema" do
|
12
|
-
|
13
|
-
expect(r["table_name"].name).to eq("table_name")
|
10
|
+
expect(SQL::Reflection.columns("information_schema.tables")).to include("table_name")
|
14
11
|
end
|
15
12
|
end
|
16
13
|
|
17
14
|
describe ".tables" do
|
18
15
|
it "returns the tables in the public schema" do
|
19
|
-
|
20
|
-
expect(r.keys).to include("users")
|
21
|
-
expect(r["users"].name).to eq("users")
|
16
|
+
expect(SQL::Reflection.tables).to include("users")
|
22
17
|
end
|
23
18
|
|
24
19
|
it "returns tables in a non-'public' schema" do
|
25
|
-
|
26
|
-
expect(r.keys).to include("information_schema.tables")
|
27
|
-
expect(r["information_schema.tables"].name).to eq("information_schema.tables")
|
28
|
-
expect(r["information_schema.tables"].table_name).to eq("tables")
|
20
|
+
expect(SQL::Reflection.tables(schema: "information_schema")).to include("information_schema.tables")
|
29
21
|
end
|
30
22
|
end
|
31
23
|
end
|
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.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radiospiel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-02-
|
12
|
+
date: 2018-02-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg_array_parser
|
@@ -185,6 +185,7 @@ files:
|
|
185
185
|
- lib/simple/sql/config.rb
|
186
186
|
- lib/simple/sql/connection.rb
|
187
187
|
- lib/simple/sql/decoder.rb
|
188
|
+
- lib/simple/sql/duplicate.rb
|
188
189
|
- lib/simple/sql/encoder.rb
|
189
190
|
- lib/simple/sql/insert.rb
|
190
191
|
- lib/simple/sql/logging.rb
|
@@ -196,6 +197,7 @@ files:
|
|
196
197
|
- spec/simple/sql_ask_spec.rb
|
197
198
|
- spec/simple/sql_config_spec.rb
|
198
199
|
- spec/simple/sql_conversion_spec.rb
|
200
|
+
- spec/simple/sql_duplicate_spec.rb
|
199
201
|
- spec/simple/sql_insert_spec.rb
|
200
202
|
- spec/simple/sql_record_spec.rb
|
201
203
|
- spec/simple/sql_reflection_spec.rb
|
@@ -235,6 +237,7 @@ test_files:
|
|
235
237
|
- spec/simple/sql_ask_spec.rb
|
236
238
|
- spec/simple/sql_config_spec.rb
|
237
239
|
- spec/simple/sql_conversion_spec.rb
|
240
|
+
- spec/simple/sql_duplicate_spec.rb
|
238
241
|
- spec/simple/sql_insert_spec.rb
|
239
242
|
- spec/simple/sql_record_spec.rb
|
240
243
|
- spec/simple/sql_reflection_spec.rb
|