better_batch 0.1.0 → 1.0.2
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/.rubocop.yml +1 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +11 -1
- data/README.md +12 -5
- data/Rakefile +79 -0
- data/lib/better_batch/.DS_Store +0 -0
- data/lib/better_batch/inputs.rb +45 -0
- data/lib/better_batch/inserted.rb +56 -0
- data/lib/better_batch/query.rb +117 -78
- data/lib/better_batch/selected.rb +79 -0
- data/lib/better_batch/updated.rb +52 -0
- data/lib/better_batch/version.rb +1 -1
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13c31343ab529bbd8df1009f601778fb504897bdfdb8b4d77f7165bad88620a1
|
4
|
+
data.tar.gz: 571f51a6631cdac4d47aca08dba3964b281b8831fb5ec7820bcd97c669814116
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38ccffb6de7831944d5c2eb7de2ea93f7ea56c7a9b392ee74fc148d4378795a3f33fdcb55e9e0f3912e7618fa6ecd26a561959c60a1d2fd4e1fd900eb59bd1f8
|
7
|
+
data.tar.gz: e2c9efca1a99bb78aabe321a5f5b5eb322743e5463e8925574512ebc2855a18572b3008570699b396ee99d6569e6ee2670fe9dd7cb5c38d41382e96c3e8145b1
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.4.2
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -11,30 +11,37 @@ For now, only Postgres is supported.
|
|
11
11
|
In your Gemfile:
|
12
12
|
```ruby
|
13
13
|
source 'https://rubygems.org'
|
14
|
-
gem '
|
14
|
+
gem 'better_batch'
|
15
15
|
```
|
16
16
|
Then:
|
17
17
|
`bundle`
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
+
If you're using ActiveRecord, you'll want to use [better_batch-active_record](https://github.com/th7/better_batch-active_record).
|
22
|
+
|
21
23
|
```ruby
|
22
24
|
table_name = :the_table
|
23
25
|
primary_key = :the_primary_key
|
24
|
-
|
26
|
+
input_columns = %i[column_a column_b column_c]
|
25
27
|
column_types = {
|
26
28
|
column_a: 'character varying(200)',
|
27
29
|
column_b: 'bigint',
|
28
30
|
column_c: 'text'
|
29
31
|
}
|
30
32
|
unique_columns = %i[column_b column_c]
|
31
|
-
|
33
|
+
now_on_insert = %i[created_at updated_at]
|
34
|
+
now_on_update = %i[updated_at]
|
35
|
+
returning = %i[id]
|
32
36
|
query = BetterBatch::Query.new(
|
33
37
|
table_name:,
|
34
38
|
primary_key:,
|
35
|
-
|
39
|
+
input_columns:,
|
36
40
|
column_types:,
|
37
|
-
unique_columns
|
41
|
+
unique_columns:,
|
42
|
+
now_on_insert:,
|
43
|
+
now_on_update:,
|
44
|
+
returning:
|
38
45
|
)
|
39
46
|
|
40
47
|
data = [
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'English'
|
3
4
|
require 'bundler/gem_tasks'
|
4
5
|
require 'rspec/core/rake_task'
|
5
6
|
|
@@ -10,3 +11,81 @@ require 'rubocop/rake_task'
|
|
10
11
|
RuboCop::RakeTask.new
|
11
12
|
|
12
13
|
task default: %i[spec rubocop]
|
14
|
+
|
15
|
+
module Tasks
|
16
|
+
BUILD_FILENAME = 'better_batch.gem'
|
17
|
+
|
18
|
+
class << self
|
19
|
+
include Rake::DSL
|
20
|
+
|
21
|
+
def install
|
22
|
+
install_release_if
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def install_release_if # rubocop:disable Metrics/MethodLength
|
28
|
+
namespace :release do
|
29
|
+
desc 'release if current version is later than last published version'
|
30
|
+
task if: :default do
|
31
|
+
assert_correct_branch!
|
32
|
+
if current_version > published_version
|
33
|
+
assert_ci!
|
34
|
+
# from bundler, asserts working directory is clean
|
35
|
+
Rake::Task['release'].invoke
|
36
|
+
end
|
37
|
+
assert_version_sane!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def published_version
|
43
|
+
@published_version ||= build_published_version
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_published_version
|
47
|
+
raw = shr('gem search --remote --exact better_batch')
|
48
|
+
Gem::Version.new(raw.split('(').last.sub(')', ''))
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_version
|
52
|
+
@current_version ||= Gem::Version.new(BetterBatch::VERSION)
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert_version_sane!
|
56
|
+
return unless current_version < published_version
|
57
|
+
|
58
|
+
raise "BetterBatch::VERSION (#{current_version}) " \
|
59
|
+
"is less than the current published (#{published_version}). " \
|
60
|
+
'Was it edited incorrectly?'
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_branch
|
64
|
+
@current_branch ||= shr('git rev-parse --abbrev-ref HEAD').chomp
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_branch
|
68
|
+
@default_branch ||= shr('git remote show origin').match(/HEAD branch: (\S+)$/)[1]
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_correct_branch!
|
72
|
+
return unless current_branch != default_branch
|
73
|
+
|
74
|
+
raise "On branch (#{current_branch}) instead of default #{default_branch}."
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_ci!
|
78
|
+
raise 'Not in CI.' unless ENV['CI'] == 'true'
|
79
|
+
end
|
80
|
+
|
81
|
+
def shr(cmd)
|
82
|
+
puts cmd
|
83
|
+
result = `#{cmd}`
|
84
|
+
raise cmd unless $CHILD_STATUS == 0
|
85
|
+
|
86
|
+
result
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Tasks.install
|
Binary file
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterBatch
|
4
|
+
Inputs = Struct.new(
|
5
|
+
:table_name,
|
6
|
+
:primary_key,
|
7
|
+
:input_columns,
|
8
|
+
:column_types,
|
9
|
+
:unique_columns,
|
10
|
+
:now_on_insert,
|
11
|
+
:now_on_update,
|
12
|
+
:returning,
|
13
|
+
keyword_init: true
|
14
|
+
)
|
15
|
+
|
16
|
+
# this strange (to me) setup avoids method redefinition warnings
|
17
|
+
module InstanceOverrides
|
18
|
+
def returning
|
19
|
+
case self[:returning]
|
20
|
+
when nil
|
21
|
+
[]
|
22
|
+
when '*', ['*']
|
23
|
+
column_types.keys
|
24
|
+
else
|
25
|
+
self[:returning]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def now_on_insert
|
30
|
+
return Array(self[:now_on_insert]) unless self[:now_on_insert].is_a?(Array)
|
31
|
+
|
32
|
+
self[:now_on_insert]
|
33
|
+
end
|
34
|
+
|
35
|
+
def now_on_update
|
36
|
+
return Array(self[:now_on_update]) unless self[:now_on_update].is_a?(Array)
|
37
|
+
|
38
|
+
self[:now_on_update]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Inputs
|
43
|
+
prepend InstanceOverrides
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module BetterBatch
|
6
|
+
class Inserted
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@inputs, :table_name, :input_columns, :unique_columns, :primary_key, :now_on_insert, :returning
|
9
|
+
|
10
|
+
TEMPLATE = <<~SQL
|
11
|
+
insert into %<table_name>s (%<input_columns_sql>s)
|
12
|
+
select distinct on (%<query_columns_sql>s)
|
13
|
+
%<select_columns_sql>s
|
14
|
+
from selected
|
15
|
+
where %<primary_key>s is null
|
16
|
+
%<returning_sql>s
|
17
|
+
SQL
|
18
|
+
|
19
|
+
def initialize(inputs)
|
20
|
+
@inputs = inputs
|
21
|
+
end
|
22
|
+
|
23
|
+
def sql
|
24
|
+
format(TEMPLATE, table_name:, primary_key:, input_columns_sql:, query_columns_sql:, select_columns_sql:,
|
25
|
+
returning_sql:)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def input_columns_sql
|
31
|
+
@input_columns_sql ||= (input_columns + now_on_insert).join(', ')
|
32
|
+
end
|
33
|
+
|
34
|
+
def select_columns_sql
|
35
|
+
@select_columns_sql ||= (input_columns + now_as).join(', ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def query_columns_sql
|
39
|
+
@query_columns_sql ||= unique_columns.join(', ')
|
40
|
+
end
|
41
|
+
|
42
|
+
def returning_sql
|
43
|
+
@returning_sql ||= build_returning_sql
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_returning_sql
|
47
|
+
return '' if returning.empty?
|
48
|
+
|
49
|
+
"returning #{((returning - input_columns) + unique_columns).join(', ')}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def now_as
|
53
|
+
@now_as ||= now_on_insert.map { |c| "now() as #{c}" }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/better_batch/query.rb
CHANGED
@@ -1,124 +1,157 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
require 'anbt-sql-formatter/formatter'
|
6
|
+
|
7
|
+
require 'better_batch'
|
8
|
+
require 'better_batch/inputs'
|
9
|
+
require 'better_batch/selected'
|
10
|
+
require 'better_batch/inserted'
|
11
|
+
require 'better_batch/updated'
|
12
|
+
|
3
13
|
module BetterBatch
|
4
14
|
class Query # rubocop:disable Metrics/ClassLength
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :@inputs, :table_name, :input_columns, :column_types, :unique_columns, :primary_key, :now_on_insert,
|
17
|
+
:now_on_update, :returning
|
18
|
+
|
5
19
|
SELECT_TEMPLATE = <<~SQL
|
6
|
-
select
|
7
|
-
|
20
|
+
select %<selected_returning>s
|
21
|
+
from (%<selected_sql>s) selected
|
22
|
+
order by %<ordinal>s
|
8
23
|
SQL
|
9
24
|
|
10
25
|
UPSERT_TEMPLATE = <<~SQL
|
11
|
-
with
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
)
|
17
|
-
%<updated_clause>s
|
18
|
-
select coalesce(selected.id, inserted.id) as id
|
19
|
-
from selected left join inserted using(%<query_columns_text>s)
|
20
|
-
order by selected.ordinal
|
26
|
+
with %<with_sql>s
|
27
|
+
select %<upsert_returning>s
|
28
|
+
from selected
|
29
|
+
%<join_sql>s
|
30
|
+
order by selected.%<ordinal>s
|
21
31
|
SQL
|
22
32
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
jsonb_to_recordset($1)
|
27
|
-
as (%<typed_columns_text>s)
|
28
|
-
) with ordinality as input(%<columns_text>s, ordinal)
|
29
|
-
left join %<table_name>s
|
30
|
-
using(%<query_columns_text>s)
|
33
|
+
UPSERT_NO_RETURN_TEMPLATE = <<~SQL
|
34
|
+
with %<with_sql>s
|
35
|
+
select true as done
|
31
36
|
SQL
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
%<columns_text>s, now() as created_at, now() as updated_at
|
37
|
-
from selected
|
38
|
-
where id is null
|
39
|
-
returning id, %<query_columns_text>s
|
40
|
-
SQL
|
38
|
+
def initialize(**)
|
39
|
+
@inputs = Inputs.new(**)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
set %<update_columns_text>s, updated_at = now()
|
45
|
-
from selected where %<table_name>s.id = selected.id
|
46
|
-
SQL
|
42
|
+
def select
|
43
|
+
raise Error, 'Select query returning nothing is invalid.' if returning.empty?
|
47
44
|
|
48
|
-
|
49
|
-
@table_name = table_name
|
50
|
-
@primary_key = primary_key
|
51
|
-
@columns = columns
|
52
|
-
@column_types = column_types
|
53
|
-
@unique_columns = unique_columns
|
45
|
+
format(SELECT_TEMPLATE, selected_returning:, selected_sql: selected.sql, ordinal: Selected::ORDINAL)
|
54
46
|
end
|
55
47
|
|
56
|
-
def
|
57
|
-
|
48
|
+
def select_formatted
|
49
|
+
format_sql(select)
|
58
50
|
end
|
59
51
|
|
60
52
|
def upsert
|
61
|
-
|
62
|
-
|
53
|
+
if returning.empty?
|
54
|
+
upsert_no_return
|
55
|
+
else
|
56
|
+
upsert_normal
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def upsert_formatted
|
61
|
+
format_sql(upsert)
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
@inputs.inspect
|
63
66
|
end
|
64
67
|
|
65
68
|
private
|
66
69
|
|
67
|
-
|
70
|
+
def upsert_no_return
|
71
|
+
format(UPSERT_NO_RETURN_TEMPLATE, with_sql:)
|
72
|
+
end
|
73
|
+
|
74
|
+
def upsert_normal
|
75
|
+
params = { with_sql:, upsert_returning:, join_sql:, ordinal: Selected::ORDINAL }
|
76
|
+
format(UPSERT_TEMPLATE, **params)
|
77
|
+
end
|
68
78
|
|
69
|
-
def
|
70
|
-
@
|
79
|
+
def with_sql
|
80
|
+
@with_sql ||= build_with_sql
|
71
81
|
end
|
72
82
|
|
73
|
-
def
|
74
|
-
|
75
|
-
query_columns_text: }
|
76
|
-
format(SELECTED_INNER_TEMPLATE, **params)
|
83
|
+
def build_with_sql
|
84
|
+
with_parts.map { |name, sql| "#{name} as (#{sql})" }.join(', ')
|
77
85
|
end
|
78
86
|
|
79
|
-
def
|
80
|
-
|
87
|
+
def with_parts
|
88
|
+
if update_columns.empty?
|
89
|
+
{ selected: selected.sql, inserted: inserted.sql }
|
90
|
+
else
|
91
|
+
{ selected: selected.sql, inserted: inserted.sql, updated: updated.sql }
|
92
|
+
end
|
81
93
|
end
|
82
94
|
|
83
|
-
def
|
84
|
-
|
95
|
+
def selected
|
96
|
+
@selected ||= Selected.new(@inputs)
|
85
97
|
end
|
86
98
|
|
87
|
-
def
|
88
|
-
@
|
99
|
+
def inserted
|
100
|
+
@inserted ||= Inserted.new(@inputs)
|
89
101
|
end
|
90
102
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
103
|
+
def updated
|
104
|
+
@updated ||= Updated.new(@inputs)
|
105
|
+
end
|
106
|
+
|
107
|
+
def selected_returning
|
108
|
+
@selected_returning ||= returning.join(', ')
|
109
|
+
end
|
110
|
+
|
111
|
+
def upsert_returning
|
112
|
+
returning.map do |col|
|
113
|
+
qualify_column(col)
|
114
|
+
end.join(', ')
|
115
|
+
end
|
116
|
+
|
117
|
+
def qualify_column(col)
|
118
|
+
if (parts = coalesce_parts(col))
|
119
|
+
"coalesce(#{parts.map { |p| "#{p}.#{col}" }.join(', ')}) as #{col}"
|
94
120
|
else
|
95
|
-
|
96
|
-
,updated as (
|
97
|
-
%<updated_inner>s
|
98
|
-
)
|
99
|
-
SQL
|
100
|
-
format(updated_clause_template, updated_inner:)
|
121
|
+
"selected.#{col}"
|
101
122
|
end
|
102
123
|
end
|
103
124
|
|
104
|
-
def
|
105
|
-
|
125
|
+
def coalesce_parts(col)
|
126
|
+
if col == primary_key || now_on_insert_only?(col)
|
127
|
+
%i[selected inserted]
|
128
|
+
elsif now_on_insert.include?(col) && now_on_update.include?(col)
|
129
|
+
%i[inserted updated selected]
|
130
|
+
end
|
106
131
|
end
|
107
132
|
|
108
|
-
def
|
109
|
-
|
133
|
+
def now_on_insert_only?(col)
|
134
|
+
now_on_insert.include?(col) && !now_on_update.include?(col)
|
110
135
|
end
|
111
136
|
|
112
|
-
def
|
113
|
-
@
|
137
|
+
def join_sql
|
138
|
+
@join_sql ||= build_join_sql
|
114
139
|
end
|
115
140
|
|
116
|
-
def
|
117
|
-
|
141
|
+
def build_join_sql
|
142
|
+
join_parts.map { |name| "left join #{name} #{using_sql}" }.join(' ')
|
143
|
+
end
|
144
|
+
|
145
|
+
def join_parts
|
146
|
+
if update_columns.empty?
|
147
|
+
[:inserted]
|
148
|
+
else
|
149
|
+
%i[inserted updated]
|
150
|
+
end
|
118
151
|
end
|
119
152
|
|
120
|
-
def
|
121
|
-
|
153
|
+
def using_sql
|
154
|
+
"using (#{query_columns_text})"
|
122
155
|
end
|
123
156
|
|
124
157
|
def query_columns_text
|
@@ -126,11 +159,17 @@ module BetterBatch
|
|
126
159
|
end
|
127
160
|
|
128
161
|
def update_columns
|
129
|
-
@update_columns ||=
|
162
|
+
@update_columns ||= input_columns - unique_columns
|
130
163
|
end
|
131
164
|
|
132
|
-
|
133
|
-
|
165
|
+
# modified from
|
166
|
+
# https://github.com/sonota88/anbt-sql-formatter/blob/main/bin/anbt-sql-formatter
|
167
|
+
def format_sql(src)
|
168
|
+
rule = AnbtSql::Rule.new
|
169
|
+
rule.keyword = AnbtSql::Rule::KEYWORD_LOWER_CASE
|
170
|
+
rule.indent_string = ' '
|
171
|
+
formatter = AnbtSql::Formatter.new(rule)
|
172
|
+
formatter.format(src)
|
134
173
|
end
|
135
174
|
end
|
136
175
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module BetterBatch
|
6
|
+
class Selected
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@inputs, :table_name, :input_columns, :column_types, :unique_columns, :primary_key, :returning
|
9
|
+
|
10
|
+
ORDINAL = :better_batch_ordinal
|
11
|
+
|
12
|
+
TEMPLATE = <<~SQL
|
13
|
+
select %<selected_returning>s
|
14
|
+
from rows from (
|
15
|
+
jsonb_to_recordset($1)
|
16
|
+
as (%<typed_columns_sql>s)
|
17
|
+
) with ordinality as input(%<input_columns_sql>s, %<ordinal>s)
|
18
|
+
left join %<table_name>s
|
19
|
+
using(%<query_columns_sql>s)
|
20
|
+
SQL
|
21
|
+
|
22
|
+
def initialize(inputs)
|
23
|
+
@inputs = inputs
|
24
|
+
end
|
25
|
+
|
26
|
+
def sql
|
27
|
+
params = { table_name:, primary_key:, selected_returning:, typed_columns_sql:, input_columns_sql:,
|
28
|
+
ordinal: ORDINAL, query_columns_sql: }
|
29
|
+
format(TEMPLATE, **params)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def selected_returning
|
35
|
+
@selected_returning ||= qualified_columns.join(', ')
|
36
|
+
end
|
37
|
+
|
38
|
+
def remaining_columns
|
39
|
+
@remaining_columns ||= returning - input_columns
|
40
|
+
end
|
41
|
+
|
42
|
+
def typed_columns_sql
|
43
|
+
@typed_columns_sql ||= input_columns.map { |c| "#{c} #{column_types[c]}" }.join(', ')
|
44
|
+
end
|
45
|
+
|
46
|
+
def input_columns_sql
|
47
|
+
@input_columns_sql ||= input_columns.join(', ')
|
48
|
+
end
|
49
|
+
|
50
|
+
def query_columns_sql
|
51
|
+
@query_columns_sql ||= unique_columns.join(', ')
|
52
|
+
end
|
53
|
+
|
54
|
+
def prefix_table(parts)
|
55
|
+
parts.map { |part| "#{table_name}.#{part}" }
|
56
|
+
end
|
57
|
+
|
58
|
+
def prefix_input(parts)
|
59
|
+
parts.map { |part| "input.#{part}" }
|
60
|
+
end
|
61
|
+
|
62
|
+
def columns
|
63
|
+
@columns ||= {
|
64
|
+
primary_key: [primary_key],
|
65
|
+
input_columns: input_columns - [primary_key],
|
66
|
+
remaining_columns: returning - [primary_key] - input_columns,
|
67
|
+
ordinal: ORDINAL
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def qualified_columns
|
72
|
+
@qualified_columns ||= \
|
73
|
+
prefix_table(columns.fetch(:primary_key)) +
|
74
|
+
prefix_table(columns.fetch(:remaining_columns)) +
|
75
|
+
prefix_input(columns.fetch(:input_columns)) +
|
76
|
+
[columns.fetch(:ordinal)]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module BetterBatch
|
6
|
+
class Updated
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@inputs, :table_name, :input_columns, :column_types, :unique_columns, :primary_key, :now_on_update,
|
9
|
+
:returning
|
10
|
+
|
11
|
+
TEMPLATE = <<~SQL
|
12
|
+
update %<table_name>s
|
13
|
+
set %<set_sql>s
|
14
|
+
from selected where %<table_name>s.%<primary_key>s = selected.%<primary_key>s
|
15
|
+
returning %<returning_sql>s
|
16
|
+
SQL
|
17
|
+
|
18
|
+
def initialize(inputs)
|
19
|
+
@inputs = inputs
|
20
|
+
end
|
21
|
+
|
22
|
+
def sql
|
23
|
+
format(TEMPLATE, table_name:, primary_key:, set_sql:, returning_sql:)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def set_sql
|
29
|
+
(update_columns_sql + now_as_sql).join(', ')
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_columns_sql
|
33
|
+
@update_columns_sql ||= update_columns.map { |c| "#{c} = selected.#{c}" }
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_columns
|
37
|
+
@update_columns ||= input_columns - unique_columns
|
38
|
+
end
|
39
|
+
|
40
|
+
def now_as_sql
|
41
|
+
@now_as_sql ||= now_on_update.map { |c| "#{c} = now()" }
|
42
|
+
end
|
43
|
+
|
44
|
+
def returning_sql
|
45
|
+
@returning_sql ||= returning_cols.map { |c| "#{table_name}.#{c}" }.join(', ')
|
46
|
+
end
|
47
|
+
|
48
|
+
def returning_cols
|
49
|
+
(returning - input_columns - now_on_update) + unique_columns + now_on_update
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/better_batch/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: better_batch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tyler Hartland
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-04-
|
11
|
-
dependencies:
|
10
|
+
date: 2025-04-15 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: anbt-sql-formatter
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.1'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.1'
|
12
26
|
email:
|
13
27
|
- tylerhartland7@gmail.com
|
14
28
|
executables: []
|
@@ -17,13 +31,19 @@ extra_rdoc_files: []
|
|
17
31
|
files:
|
18
32
|
- ".rspec"
|
19
33
|
- ".rubocop.yml"
|
34
|
+
- ".ruby-version"
|
20
35
|
- CHANGELOG.md
|
21
36
|
- CODE_OF_CONDUCT.md
|
22
37
|
- LICENSE.txt
|
23
38
|
- README.md
|
24
39
|
- Rakefile
|
25
40
|
- lib/better_batch.rb
|
41
|
+
- lib/better_batch/.DS_Store
|
42
|
+
- lib/better_batch/inputs.rb
|
43
|
+
- lib/better_batch/inserted.rb
|
26
44
|
- lib/better_batch/query.rb
|
45
|
+
- lib/better_batch/selected.rb
|
46
|
+
- lib/better_batch/updated.rb
|
27
47
|
- lib/better_batch/version.rb
|
28
48
|
- sig/better_batch.rbs
|
29
49
|
homepage: https://github.com/th7/better_batch
|
@@ -41,7 +61,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
61
|
requirements:
|
42
62
|
- - ">="
|
43
63
|
- !ruby/object:Gem::Version
|
44
|
-
version: 3.
|
64
|
+
version: 3.2.0
|
45
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
66
|
requirements:
|
47
67
|
- - ">="
|