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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cad4b0118fd0c649c4f6050f6f641245292e0b4d
4
- data.tar.gz: 8e12a2a3b15218a02f3a2e7464aaec7ea84e39ae
3
+ metadata.gz: 00ca6e591d2e1aecbf57de4b27c3693e559c6ce5
4
+ data.tar.gz: c61d3dfa4eeede7e8e1c5a5c59cac3f6c788e197
5
5
  SHA512:
6
- metadata.gz: acf9fe4aacd51c3a34a34a92d45b339daa673f703d8e933f31e319057c571a3c1cae8b1d181ac8700d1953b18a8f5fd83ed57262e82415adedc662fd5701418c
7
- data.tar.gz: 2078e081ab1d2110a1c582a1861d617a201d37ab76896ff05d8a9f5b9f01cc3630bd430a0ae070a48d8fbff473587b3aa775e52486f5f99fd9a2097a54afe11e
6
+ metadata.gz: 6d9fe86fe3a97443857b51c33294512be9f9a9b555b4783b4807ad85fd2c51a948b4cae4ba96dfb941ae286fa6870cabd777c161517d4c7c14a00ff6c234846a
7
+ data.tar.gz: 94b1a01d68fd2b2ddc6ec5aceb9e7dda4dce2c86aed7a3bae2f310278004d221652ddd88ada42f583d59d236bfddcfa511f93a6dafa753b2b521b8b2ad802bd0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple-sql (0.2.7)
4
+ simple-sql (0.2.8)
5
5
  pg (~> 0.20)
6
6
  pg_array_parser (~> 0)
7
7
 
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
@@ -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 = timestamp_columns_in_table(table_name) - columns.map(&:to_s)
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
- select = if schema == "public"
13
- "table_name AS name, *"
14
- else
15
- "table_schema || '.' || table_name AS name, *"
16
- end
17
-
18
- records = ::Simple::SQL.records <<~SQL, schema
19
- SELECT #{select}
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(records, :name)
55
+ records_by_attr(recs, :name)
24
56
  end
25
57
 
26
- def columns(table_name)
58
+ def column_info(table_name)
27
59
  schema, table_name = parse_table_name(table_name)
28
- records = ::Simple::SQL.records <<~SQL, schema, table_name
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(records, :column_name)
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
@@ -1,5 +1,5 @@
1
1
  module Simple
2
2
  module SQL
3
- VERSION = "0.2.7"
3
+ VERSION = "0.2.8"
4
4
  end
5
5
  end
@@ -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
+
@@ -1,10 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Simple::SQL.insert" do
4
- def expects(expected_result, sql, *args)
5
- expect(SQL.record(sql, *args)).to eq(expected_result)
6
- end
7
-
8
4
  let!(:users) { 1.upto(USER_COUNT).map { create(:user) } }
9
5
 
10
6
  it "inserts a single user" do
@@ -1,10 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Simple::SQL.record" do
4
- def expects(expected_result, sql, *args)
5
- expect(SQL.record(sql, *args)).to eq(expected_result)
6
- end
7
-
8
4
  let!(:users) { 1.upto(USER_COUNT).map { create(:user) } }
9
5
 
10
6
  it "calls the database" do
@@ -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
- r = SQL::Reflection.columns("users")
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
- r = SQL::Reflection.columns("information_schema.tables")
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
- r = SQL::Reflection.tables
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
- r = SQL::Reflection.tables(schema: "information_schema")
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.7
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-19 00:00:00.000000000 Z
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