pg_online_schema_change 0.7.4 → 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|