simple-sql 0.2.7 → 0.2.8

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
  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