pg_online_schema_change 0.7.4 → 0.7.5
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_todo.yml +10 -5
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +3 -3
- data/docker-compose.yml +1 -1
- data/lib/pg_online_schema_change/client.rb +2 -1
- data/lib/pg_online_schema_change/helper.rb +1 -1
- data/lib/pg_online_schema_change/orchestrate.rb +27 -24
- data/lib/pg_online_schema_change/query.rb +27 -4
- data/lib/pg_online_schema_change/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 973cb542595ffacbaa942636be7b366e018a2141a75c1fd73e38e65e263ddd39
|
4
|
+
data.tar.gz: abe5178f45a6c8a00c0b64a5cee543b7fc22e126d7a5744adefada83fa6ab844
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 718ae2f4b7c1f0319f9c36d5c5b6f24441ac94e108fb281d357cec5790ee68ba4fdd1237aae4d414e284ccbf8228796027b9e2d6ffb19730bfe8260acb8120ba
|
7
|
+
data.tar.gz: 030dbca6e2f7b2a4f117a6b639f7c1454e7df5514adf4c81f3957d0a2310ce62042ea16d9707f6b7f7590c4f2b04f4276de33100bfc7a1611a1df388a21f8e5e
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2023-03-12 14:28:52 UTC using RuboCop version 1.23.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
@@ -9,13 +9,18 @@
|
|
9
9
|
# Offense count: 2
|
10
10
|
# Configuration parameters: CountComments, CountAsOne.
|
11
11
|
Metrics/ClassLength:
|
12
|
-
Max:
|
12
|
+
Max: 285
|
13
13
|
|
14
14
|
# Offense count: 2
|
15
15
|
# Configuration parameters: IgnoredMethods.
|
16
16
|
Metrics/CyclomaticComplexity:
|
17
17
|
Max: 15
|
18
18
|
|
19
|
+
# Offense count: 1
|
20
|
+
# Configuration parameters: CountComments, CountAsOne.
|
21
|
+
Metrics/ModuleLength:
|
22
|
+
Max: 112
|
23
|
+
|
19
24
|
# Offense count: 2
|
20
25
|
# Configuration parameters: IgnoredMethods.
|
21
26
|
Metrics/PerceivedComplexity:
|
@@ -26,14 +31,14 @@ Packaging/GemspecGit:
|
|
26
31
|
Exclude:
|
27
32
|
- 'pg_online_schema_change.gemspec'
|
28
33
|
|
29
|
-
# Offense count:
|
34
|
+
# Offense count: 69
|
30
35
|
# Configuration parameters: CountAsOne.
|
31
36
|
RSpec/ExampleLength:
|
32
37
|
Max: 55
|
33
38
|
|
34
|
-
# Offense count:
|
39
|
+
# Offense count: 25
|
35
40
|
RSpec/MultipleExpectations:
|
36
|
-
Max:
|
41
|
+
Max: 14
|
37
42
|
|
38
43
|
# Offense count: 6
|
39
44
|
# Configuration parameters: AllowedMethods.
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
|
2
|
+
|
3
|
+
## [0.7.4] - 2022-09-24
|
4
|
+
* off-by-one: Don't skip PK sequence value by one https://github.com/shayonj/pg-osc/pull/74
|
5
|
+
|
2
6
|
## [0.7.3] - 2022-09-24
|
3
7
|
* Update primary key sequence on shadow table https://github.com/shayonj/pg-osc/pull/72
|
4
8
|
- Thanks to @brycethornton for the report
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pg_online_schema_change (0.7.
|
4
|
+
pg_online_schema_change (0.7.5)
|
5
5
|
ougai (~> 2.0.0)
|
6
6
|
pg (~> 1.3.2)
|
7
7
|
pg_query (~> 2.1.3)
|
@@ -13,7 +13,7 @@ GEM
|
|
13
13
|
ast (2.4.2)
|
14
14
|
coderay (1.1.3)
|
15
15
|
diff-lcs (1.5.0)
|
16
|
-
google-protobuf (3.21.
|
16
|
+
google-protobuf (3.21.7)
|
17
17
|
method_source (1.0.0)
|
18
18
|
oj (3.13.21)
|
19
19
|
ougai (2.0.0)
|
@@ -85,4 +85,4 @@ DEPENDENCIES
|
|
85
85
|
rubocop-rspec (~> 2.7.0)
|
86
86
|
|
87
87
|
BUNDLED WITH
|
88
|
-
2.3.
|
88
|
+
2.3.24
|
data/docker-compose.yml
CHANGED
@@ -4,7 +4,7 @@ require "pg"
|
|
4
4
|
|
5
5
|
module PgOnlineSchemaChange
|
6
6
|
class Client
|
7
|
-
attr_accessor :alter_statement, :schema, :dbname, :host, :username, :port, :password, :connection, :table, :drop,
|
7
|
+
attr_accessor :alter_statement, :schema, :dbname, :host, :username, :port, :password, :connection, :table, :table_name, :drop,
|
8
8
|
:kill_backends, :wait_time_for_lock, :copy_statement, :pull_batch_count, :delta_count
|
9
9
|
|
10
10
|
def initialize(options)
|
@@ -33,6 +33,7 @@ module PgOnlineSchemaChange
|
|
33
33
|
)
|
34
34
|
|
35
35
|
@table = Query.table(@alter_statement)
|
36
|
+
@table_name = Query.table_name(@alter_statement, @table)
|
36
37
|
|
37
38
|
PgOnlineSchemaChange.logger.debug("Connection established")
|
38
39
|
end
|
@@ -30,16 +30,17 @@ module PgOnlineSchemaChange
|
|
30
30
|
# Set this early on to ensure their creation and cleanup (unexpected)
|
31
31
|
# happens at all times. IOW, the calls from Store.get always return
|
32
32
|
# the same value.
|
33
|
-
Store.set(:old_primary_table, "pgosc_op_table_#{client.table}")
|
34
|
-
Store.set(:audit_table, "pgosc_at_#{client.table}_#{pgosc_identifier}")
|
33
|
+
Store.set(:old_primary_table, "pgosc_op_table_#{client.table.downcase}")
|
34
|
+
Store.set(:audit_table, "pgosc_at_#{client.table.downcase}_#{pgosc_identifier}")
|
35
35
|
Store.set(:operation_type_column, "operation_type_#{pgosc_identifier}")
|
36
36
|
Store.set(:trigger_time_column, "trigger_time_#{pgosc_identifier}")
|
37
37
|
Store.set(:audit_table_pk, "at_#{pgosc_identifier}_id")
|
38
38
|
Store.set(:audit_table_pk_sequence, "#{audit_table}_#{audit_table_pk}_seq")
|
39
|
-
Store.set(:shadow_table, "pgosc_st_#{client.table}_#{pgosc_identifier}")
|
39
|
+
Store.set(:shadow_table, "pgosc_st_#{client.table.downcase}_#{pgosc_identifier}")
|
40
40
|
|
41
|
-
Store.set(:referential_foreign_key_statements, Query.referential_foreign_keys_to_refresh(client, client.
|
42
|
-
Store.set(:self_foreign_key_statements, Query.self_foreign_keys_to_refresh(client, client.
|
41
|
+
Store.set(:referential_foreign_key_statements, Query.referential_foreign_keys_to_refresh(client, client.table_name))
|
42
|
+
Store.set(:self_foreign_key_statements, Query.self_foreign_keys_to_refresh(client, client.table_name))
|
43
|
+
Store.set(:trigger_statements, Query.get_triggers_for(client, client.table_name))
|
43
44
|
end
|
44
45
|
|
45
46
|
def run!(options)
|
@@ -49,6 +50,7 @@ module PgOnlineSchemaChange
|
|
49
50
|
raise Error, "Parent table has no primary key, exiting..." if primary_key.nil?
|
50
51
|
|
51
52
|
setup_audit_table!
|
53
|
+
|
52
54
|
setup_trigger!
|
53
55
|
setup_shadow_table! # re-uses transaction with serializable
|
54
56
|
disable_vacuum! # re-uses transaction with serializable
|
@@ -98,7 +100,7 @@ module PgOnlineSchemaChange
|
|
98
100
|
logger.info("Setting up audit table", { audit_table: audit_table })
|
99
101
|
|
100
102
|
sql = <<~SQL
|
101
|
-
CREATE TABLE #{audit_table} (#{audit_table_pk} SERIAL PRIMARY KEY, #{operation_type_column} text, #{trigger_time_column} timestamp, LIKE #{client.
|
103
|
+
CREATE TABLE #{audit_table} (#{audit_table_pk} SERIAL PRIMARY KEY, #{operation_type_column} text, #{trigger_time_column} timestamp, LIKE #{client.table_name});
|
102
104
|
SQL
|
103
105
|
|
104
106
|
Query.run(client.connection, sql)
|
@@ -108,14 +110,14 @@ module PgOnlineSchemaChange
|
|
108
110
|
# acquire access exclusive lock to ensure audit triggers
|
109
111
|
# are setup fine. This also calls kill_backends (if opted in via flag)
|
110
112
|
# so any competing backends will be killed to setup the trigger
|
111
|
-
opened = Query.open_lock_exclusive(client, client.
|
113
|
+
opened = Query.open_lock_exclusive(client, client.table_name)
|
112
114
|
|
113
115
|
raise AccessExclusiveLockNotAcquired unless opened
|
114
116
|
|
115
117
|
logger.info("Setting up triggers")
|
116
118
|
|
117
119
|
sql = <<~SQL
|
118
|
-
DROP TRIGGER IF EXISTS primary_to_audit_table_trigger ON #{client.
|
120
|
+
DROP TRIGGER IF EXISTS primary_to_audit_table_trigger ON #{client.table_name};
|
119
121
|
|
120
122
|
CREATE OR REPLACE FUNCTION primary_to_audit_table_trigger()
|
121
123
|
RETURNS TRIGGER AS
|
@@ -135,7 +137,7 @@ module PgOnlineSchemaChange
|
|
135
137
|
$$ LANGUAGE PLPGSQL SECURITY DEFINER;
|
136
138
|
|
137
139
|
CREATE TRIGGER primary_to_audit_table_trigger
|
138
|
-
AFTER INSERT OR UPDATE OR DELETE ON #{client.
|
140
|
+
AFTER INSERT OR UPDATE OR DELETE ON #{client.table_name}
|
139
141
|
FOR EACH ROW EXECUTE PROCEDURE primary_to_audit_table_trigger();
|
140
142
|
SQL
|
141
143
|
|
@@ -156,16 +158,16 @@ module PgOnlineSchemaChange
|
|
156
158
|
Query.run(client.connection, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", true)
|
157
159
|
logger.info("Setting up shadow table", { shadow_table: shadow_table })
|
158
160
|
|
159
|
-
Query.run(client.connection, "SELECT create_table_all('#{client.
|
161
|
+
Query.run(client.connection, "SELECT create_table_all('#{client.table_name}', '#{shadow_table}');", true)
|
160
162
|
|
161
163
|
# update serials
|
162
|
-
Query.run(client.connection, "SELECT fix_serial_sequence('#{client.
|
164
|
+
Query.run(client.connection, "SELECT fix_serial_sequence('#{client.table_name}', '#{shadow_table}');", true)
|
163
165
|
end
|
164
166
|
|
165
167
|
def disable_vacuum!
|
166
168
|
# re-uses transaction with serializable
|
167
169
|
# Disabling vacuum to avoid any issues during the process
|
168
|
-
result = Query.storage_parameters_for(client, client.
|
170
|
+
result = Query.storage_parameters_for(client, client.table_name, true) || ""
|
169
171
|
Store.set(:primary_table_storage_parameters, result)
|
170
172
|
|
171
173
|
logger.debug("Disabling vacuum on shadow and audit table",
|
@@ -186,7 +188,7 @@ module PgOnlineSchemaChange
|
|
186
188
|
# re-uses transaction with serializable
|
187
189
|
statement = Query.alter_statement_for(client, shadow_table)
|
188
190
|
logger.info("Running alter statement on shadow table",
|
189
|
-
{ shadow_table: shadow_table, parent_table: client.
|
191
|
+
{ shadow_table: shadow_table, parent_table: client.table_name })
|
190
192
|
Query.run(client.connection, statement, true)
|
191
193
|
|
192
194
|
Store.set(:dropped_columns_list, Query.dropped_columns(client))
|
@@ -198,10 +200,10 @@ module PgOnlineSchemaChange
|
|
198
200
|
# Begin the process to copy data into copy table
|
199
201
|
# depending on the size of the table, this can be a time
|
200
202
|
# taking operation.
|
201
|
-
logger.info("Clearing contents of audit table before copy..", { shadow_table: shadow_table, parent_table: client.
|
203
|
+
logger.info("Clearing contents of audit table before copy..", { shadow_table: shadow_table, parent_table: client.table_name })
|
202
204
|
Query.run(client.connection, "DELETE FROM #{audit_table}", true)
|
203
205
|
|
204
|
-
logger.info("Copying contents..", { shadow_table: shadow_table, parent_table: client.
|
206
|
+
logger.info("Copying contents..", { shadow_table: shadow_table, parent_table: client.table_name })
|
205
207
|
if client.copy_statement
|
206
208
|
query = format(client.copy_statement, shadow_table: shadow_table)
|
207
209
|
return Query.run(client.connection, query, true)
|
@@ -224,12 +226,12 @@ module PgOnlineSchemaChange
|
|
224
226
|
def swap!
|
225
227
|
logger.info("Performing swap!")
|
226
228
|
|
227
|
-
storage_params_reset = primary_table_storage_parameters.empty? ? "" : "ALTER TABLE #{client.
|
229
|
+
storage_params_reset = primary_table_storage_parameters.empty? ? "" : "ALTER TABLE #{client.table_name} SET (#{primary_table_storage_parameters});"
|
228
230
|
|
229
231
|
# From here on, all statements are carried out in a single
|
230
232
|
# transaction with access exclusive lock
|
231
233
|
|
232
|
-
opened = Query.open_lock_exclusive(client, client.
|
234
|
+
opened = Query.open_lock_exclusive(client, client.table_name)
|
233
235
|
|
234
236
|
raise AccessExclusiveLockNotAcquired unless opened
|
235
237
|
|
@@ -238,16 +240,17 @@ module PgOnlineSchemaChange
|
|
238
240
|
rows = Replay.rows_to_play(opened)
|
239
241
|
Replay.play!(rows, opened)
|
240
242
|
|
241
|
-
query_for_primary_key_refresh = Query.query_for_primary_key_refresh(shadow_table, primary_key, client.
|
243
|
+
query_for_primary_key_refresh = Query.query_for_primary_key_refresh(shadow_table, primary_key, client.table_name, opened)
|
242
244
|
|
243
245
|
sql = <<~SQL
|
244
246
|
#{query_for_primary_key_refresh};
|
245
|
-
ALTER TABLE #{client.
|
246
|
-
ALTER TABLE #{shadow_table} RENAME to #{client.
|
247
|
+
ALTER TABLE #{client.table_name} RENAME to #{old_primary_table};
|
248
|
+
ALTER TABLE #{shadow_table} RENAME to #{client.table_name};
|
247
249
|
#{referential_foreign_key_statements}
|
248
250
|
#{self_foreign_key_statements}
|
251
|
+
#{trigger_statements}
|
249
252
|
#{storage_params_reset}
|
250
|
-
DROP TRIGGER IF EXISTS primary_to_audit_table_trigger ON #{client.
|
253
|
+
DROP TRIGGER IF EXISTS primary_to_audit_table_trigger ON #{client.table_name};
|
251
254
|
SQL
|
252
255
|
|
253
256
|
Query.run(client.connection, sql, opened)
|
@@ -259,13 +262,13 @@ module PgOnlineSchemaChange
|
|
259
262
|
def run_analyze!
|
260
263
|
logger.info("Performing ANALYZE!")
|
261
264
|
|
262
|
-
Query.run(client.connection, "ANALYZE VERBOSE #{client.
|
265
|
+
Query.run(client.connection, "ANALYZE VERBOSE #{client.table_name};")
|
263
266
|
end
|
264
267
|
|
265
268
|
def validate_constraints!
|
266
269
|
logger.info("Validating constraints!")
|
267
270
|
|
268
|
-
validate_statements = Query.get_foreign_keys_to_validate(client, client.
|
271
|
+
validate_statements = Query.get_foreign_keys_to_validate(client, client.table_name)
|
269
272
|
|
270
273
|
Query.run(client.connection, validate_statements)
|
271
274
|
end
|
@@ -276,7 +279,7 @@ module PgOnlineSchemaChange
|
|
276
279
|
shadow_table_drop = shadow_table ? "DROP TABLE IF EXISTS #{shadow_table}" : ""
|
277
280
|
|
278
281
|
sql = <<~SQL
|
279
|
-
DROP TRIGGER IF EXISTS primary_to_audit_table_trigger ON #{client.
|
282
|
+
DROP TRIGGER IF EXISTS primary_to_audit_table_trigger ON #{client.table_name};
|
280
283
|
#{audit_table_drop};
|
281
284
|
#{shadow_table_drop};
|
282
285
|
#{primary_drop}
|
@@ -42,6 +42,15 @@ module PgOnlineSchemaChange
|
|
42
42
|
PgQuery.parse(query).tables[0] || from_rename_statement
|
43
43
|
end
|
44
44
|
|
45
|
+
def table_name(query, table)
|
46
|
+
table_name = "\"#{table}\""
|
47
|
+
if table =~ /[A-Z]/ && (query.include? table_name) && table[0] != '"'
|
48
|
+
table_name
|
49
|
+
else
|
50
|
+
table
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
45
54
|
def run(connection, query, reuse_trasaction = false, &block)
|
46
55
|
connection.cancel if [PG::PQTRANS_INERROR, PG::PQTRANS_UNKNOWN].include?(connection.transaction_status)
|
47
56
|
|
@@ -65,7 +74,7 @@ module PgOnlineSchemaChange
|
|
65
74
|
def table_columns(client, table = nil, reuse_trasaction = false)
|
66
75
|
sql = <<~SQL
|
67
76
|
SELECT attname as column_name, format_type(atttypid, atttypmod) as type, attnum as column_position FROM pg_attribute
|
68
|
-
WHERE attrelid = \'#{table || client.
|
77
|
+
WHERE attrelid = \'#{table || client.table_name}\'::regclass AND attnum > 0 AND NOT attisdropped
|
69
78
|
ORDER BY attnum;
|
70
79
|
SQL
|
71
80
|
mapped_columns = []
|
@@ -108,6 +117,20 @@ module PgOnlineSchemaChange
|
|
108
117
|
indexes
|
109
118
|
end
|
110
119
|
|
120
|
+
def get_triggers_for(client, table)
|
121
|
+
query = <<~SQL
|
122
|
+
SELECT pg_get_triggerdef(oid) as tdef FROM pg_trigger
|
123
|
+
WHERE tgrelid = \'#{client.schema}.#{table}\'::regclass AND tgisinternal = FALSE;
|
124
|
+
SQL
|
125
|
+
|
126
|
+
triggers = []
|
127
|
+
run(client.connection, query) do |result|
|
128
|
+
triggers = result.map { |row| "#{row["tdef"]};" }
|
129
|
+
end
|
130
|
+
|
131
|
+
triggers.join(";")
|
132
|
+
end
|
133
|
+
|
111
134
|
def get_all_constraints_for(client)
|
112
135
|
query = <<~SQL
|
113
136
|
SELECT conrelid::regclass AS table_on,
|
@@ -255,7 +278,7 @@ module PgOnlineSchemaChange
|
|
255
278
|
|
256
279
|
query = <<~SQL
|
257
280
|
SET lock_timeout = '#{client.wait_time_for_lock}s';
|
258
|
-
LOCK TABLE #{client.
|
281
|
+
LOCK TABLE #{client.table_name} IN ACCESS EXCLUSIVE MODE;
|
259
282
|
SQL
|
260
283
|
run(client.connection, query, true)
|
261
284
|
|
@@ -289,7 +312,7 @@ module PgOnlineSchemaChange
|
|
289
312
|
end
|
290
313
|
|
291
314
|
def copy_data_statement(client, shadow_table, reuse_trasaction = false)
|
292
|
-
select_columns = table_columns(client, client.
|
315
|
+
select_columns = table_columns(client, client.table_name, reuse_trasaction).map do |entry|
|
293
316
|
entry["column_name_regular"]
|
294
317
|
end
|
295
318
|
|
@@ -316,7 +339,7 @@ module PgOnlineSchemaChange
|
|
316
339
|
<<~SQL
|
317
340
|
INSERT INTO #{shadow_table}(#{insert_into_columns.join(", ")})
|
318
341
|
SELECT #{select_columns.join(", ")}
|
319
|
-
FROM ONLY #{client.
|
342
|
+
FROM ONLY #{client.table_name}
|
320
343
|
SQL
|
321
344
|
end
|
322
345
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_online_schema_change
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shayon Mukherjee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ougai
|