pg_online_schema_change 0.7.5 → 0.8.0
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/.prettierignore +4 -0
- data/.prettierrc +13 -0
- data/.rubocop.yml +204 -75
- data/.rubocop_todo.yml +11 -30
- data/.ruby-version +1 -1
- data/CHANGELOG.md +35 -21
- data/CODE_OF_CONDUCT.md +11 -11
- data/Gemfile.lock +72 -41
- data/README.md +40 -24
- data/Rakefile +1 -1
- data/docker-compose.yml +1 -1
- data/docs/load-test.md +11 -10
- data/lib/pg_online_schema_change/cli.rb +87 -23
- data/lib/pg_online_schema_change/client.rb +23 -12
- data/lib/pg_online_schema_change/orchestrate.rb +59 -24
- data/lib/pg_online_schema_change/query.rb +128 -98
- data/lib/pg_online_schema_change/replay.rb +10 -20
- data/lib/pg_online_schema_change/version.rb +1 -1
- data/lib/pg_online_schema_change.rb +15 -9
- data/package.json +13 -0
- data/yarn.lock +15 -0
- metadata +96 -36
@@ -38,8 +38,14 @@ module PgOnlineSchemaChange
|
|
38
38
|
Store.set(:audit_table_pk_sequence, "#{audit_table}_#{audit_table_pk}_seq")
|
39
39
|
Store.set(:shadow_table, "pgosc_st_#{client.table.downcase}_#{pgosc_identifier}")
|
40
40
|
|
41
|
-
Store.set(
|
42
|
-
|
41
|
+
Store.set(
|
42
|
+
:referential_foreign_key_statements,
|
43
|
+
Query.referential_foreign_keys_to_refresh(client, client.table_name),
|
44
|
+
)
|
45
|
+
Store.set(
|
46
|
+
:self_foreign_key_statements,
|
47
|
+
Query.self_foreign_keys_to_refresh(client, client.table_name),
|
48
|
+
)
|
43
49
|
Store.set(:trigger_statements, Query.get_triggers_for(client, client.table_name))
|
44
50
|
end
|
45
51
|
|
@@ -74,9 +80,7 @@ module PgOnlineSchemaChange
|
|
74
80
|
def setup_signals!
|
75
81
|
reader, writer = IO.pipe
|
76
82
|
|
77
|
-
|
78
|
-
trap(sig) { writer.puts sig }
|
79
|
-
end
|
83
|
+
['TERM', 'QUIT', 'INT'].each { |sig| trap(sig) { writer.puts sig } }
|
80
84
|
|
81
85
|
reader
|
82
86
|
end
|
@@ -85,14 +89,14 @@ module PgOnlineSchemaChange
|
|
85
89
|
reader = setup_signals!
|
86
90
|
signal = reader.gets.chomp
|
87
91
|
|
88
|
-
while !reader.closed? &&
|
89
|
-
logger.info
|
92
|
+
while !reader.closed? && reader.wait_readable # rubocop:disable Lint/UnreachableLoop
|
93
|
+
logger.info("Signal #{signal} received, cleaning up")
|
90
94
|
|
91
95
|
client.connection.cancel
|
92
96
|
drop_and_cleanup!
|
93
97
|
reader.close
|
94
98
|
|
95
|
-
exit
|
99
|
+
exit(Signal.list[signal])
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
@@ -124,13 +128,13 @@ module PgOnlineSchemaChange
|
|
124
128
|
$$
|
125
129
|
BEGIN
|
126
130
|
IF ( TG_OP = 'INSERT') THEN
|
127
|
-
INSERT INTO
|
131
|
+
INSERT INTO "#{audit_table}" select nextval('#{audit_table_pk_sequence}'), 'INSERT', clock_timestamp(), NEW.* ;
|
128
132
|
RETURN NEW;
|
129
133
|
ELSIF ( TG_OP = 'UPDATE') THEN
|
130
|
-
INSERT INTO
|
134
|
+
INSERT INTO "#{audit_table}" select nextval('#{audit_table_pk_sequence}'), 'UPDATE', clock_timestamp(), NEW.* ;
|
131
135
|
RETURN NEW;
|
132
136
|
ELSIF ( TG_OP = 'DELETE') THEN
|
133
|
-
INSERT INTO
|
137
|
+
INSERT INTO "#{audit_table}" select nextval('#{audit_table_pk_sequence}'), 'DELETE', clock_timestamp(), OLD.* ;
|
134
138
|
RETURN NEW;
|
135
139
|
END IF;
|
136
140
|
END;
|
@@ -158,10 +162,18 @@ module PgOnlineSchemaChange
|
|
158
162
|
Query.run(client.connection, "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE", true)
|
159
163
|
logger.info("Setting up shadow table", { shadow_table: shadow_table })
|
160
164
|
|
161
|
-
Query.run(
|
165
|
+
Query.run(
|
166
|
+
client.connection,
|
167
|
+
"SELECT create_table_all('#{client.table_name}', '#{shadow_table}');",
|
168
|
+
true,
|
169
|
+
)
|
162
170
|
|
163
171
|
# update serials
|
164
|
-
Query.run(
|
172
|
+
Query.run(
|
173
|
+
client.connection,
|
174
|
+
"SELECT fix_serial_sequence('#{client.table_name}', '#{shadow_table}');",
|
175
|
+
true,
|
176
|
+
)
|
165
177
|
end
|
166
178
|
|
167
179
|
def disable_vacuum!
|
@@ -170,8 +182,10 @@ module PgOnlineSchemaChange
|
|
170
182
|
result = Query.storage_parameters_for(client, client.table_name, true) || ""
|
171
183
|
Store.set(:primary_table_storage_parameters, result)
|
172
184
|
|
173
|
-
logger.debug(
|
174
|
-
|
185
|
+
logger.debug(
|
186
|
+
"Disabling vacuum on shadow and audit table",
|
187
|
+
{ shadow_table: shadow_table, audit_table: audit_table },
|
188
|
+
)
|
175
189
|
sql = <<~SQL
|
176
190
|
ALTER TABLE #{shadow_table} SET (
|
177
191
|
autovacuum_enabled = false, toast.autovacuum_enabled = false
|
@@ -187,8 +201,10 @@ module PgOnlineSchemaChange
|
|
187
201
|
def run_alter_statement!
|
188
202
|
# re-uses transaction with serializable
|
189
203
|
statement = Query.alter_statement_for(client, shadow_table)
|
190
|
-
logger.info(
|
191
|
-
|
204
|
+
logger.info(
|
205
|
+
"Running alter statement on shadow table",
|
206
|
+
{ shadow_table: shadow_table, parent_table: client.table_name },
|
207
|
+
)
|
192
208
|
Query.run(client.connection, statement, true)
|
193
209
|
|
194
210
|
Store.set(:dropped_columns_list, Query.dropped_columns(client))
|
@@ -200,10 +216,16 @@ module PgOnlineSchemaChange
|
|
200
216
|
# Begin the process to copy data into copy table
|
201
217
|
# depending on the size of the table, this can be a time
|
202
218
|
# taking operation.
|
203
|
-
logger.info(
|
219
|
+
logger.info(
|
220
|
+
"Clearing contents of audit table before copy..",
|
221
|
+
{ shadow_table: shadow_table, parent_table: client.table_name },
|
222
|
+
)
|
204
223
|
Query.run(client.connection, "DELETE FROM #{audit_table}", true)
|
205
224
|
|
206
|
-
logger.info(
|
225
|
+
logger.info(
|
226
|
+
"Copying contents..",
|
227
|
+
{ shadow_table: shadow_table, parent_table: client.table_name },
|
228
|
+
)
|
207
229
|
if client.copy_statement
|
208
230
|
query = format(client.copy_statement, shadow_table: shadow_table)
|
209
231
|
return Query.run(client.connection, query, true)
|
@@ -225,9 +247,6 @@ module PgOnlineSchemaChange
|
|
225
247
|
|
226
248
|
def swap!
|
227
249
|
logger.info("Performing swap!")
|
228
|
-
|
229
|
-
storage_params_reset = primary_table_storage_parameters.empty? ? "" : "ALTER TABLE #{client.table_name} SET (#{primary_table_storage_parameters});"
|
230
|
-
|
231
250
|
# From here on, all statements are carried out in a single
|
232
251
|
# transaction with access exclusive lock
|
233
252
|
|
@@ -235,12 +254,17 @@ module PgOnlineSchemaChange
|
|
235
254
|
|
236
255
|
raise AccessExclusiveLockNotAcquired unless opened
|
237
256
|
|
238
|
-
Query.run(
|
257
|
+
Query.run(
|
258
|
+
client.connection,
|
259
|
+
"SET statement_timeout to '#{SWAP_STATEMENT_TIMEOUT}';",
|
260
|
+
opened,
|
261
|
+
)
|
239
262
|
|
240
263
|
rows = Replay.rows_to_play(opened)
|
241
264
|
Replay.play!(rows, opened)
|
242
265
|
|
243
|
-
query_for_primary_key_refresh =
|
266
|
+
query_for_primary_key_refresh =
|
267
|
+
Query.query_for_primary_key_refresh(shadow_table, primary_key, client.table_name, opened)
|
244
268
|
|
245
269
|
sql = <<~SQL
|
246
270
|
#{query_for_primary_key_refresh};
|
@@ -296,6 +320,17 @@ module PgOnlineSchemaChange
|
|
296
320
|
def pgosc_identifier
|
297
321
|
@pgosc_identifier ||= SecureRandom.hex(3)
|
298
322
|
end
|
323
|
+
|
324
|
+
def storage_params_reset
|
325
|
+
"ALTER TABLE #{client.table_name} RESET (autovacuum_enabled, toast.autovacuum_enabled);" +
|
326
|
+
(
|
327
|
+
if primary_table_storage_parameters.empty?
|
328
|
+
""
|
329
|
+
else
|
330
|
+
"ALTER TABLE #{client.table_name} SET (#{primary_table_storage_parameters});"
|
331
|
+
end
|
332
|
+
)
|
333
|
+
end
|
299
334
|
end
|
300
335
|
end
|
301
336
|
end
|
@@ -14,21 +14,31 @@ module PgOnlineSchemaChange
|
|
14
14
|
|
15
15
|
class << self
|
16
16
|
def alter_statement?(query)
|
17
|
-
PgQuery
|
18
|
-
|
19
|
-
|
17
|
+
PgQuery
|
18
|
+
.parse(query)
|
19
|
+
.tree
|
20
|
+
.stmts
|
21
|
+
.all? do |statement|
|
22
|
+
statement.stmt.alter_table_stmt.instance_of?(PgQuery::AlterTableStmt) ||
|
23
|
+
statement.stmt.rename_stmt.instance_of?(PgQuery::RenameStmt)
|
24
|
+
end
|
20
25
|
rescue PgQuery::ParseError
|
21
26
|
false
|
22
27
|
end
|
23
28
|
|
24
29
|
def same_table?(query)
|
25
|
-
tables =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
tables =
|
31
|
+
PgQuery
|
32
|
+
.parse(query)
|
33
|
+
.tree
|
34
|
+
.stmts
|
35
|
+
.filter_map do |statement|
|
36
|
+
if statement.stmt.alter_table_stmt.instance_of?(PgQuery::AlterTableStmt)
|
37
|
+
statement.stmt.alter_table_stmt.relation.relname
|
38
|
+
elsif statement.stmt.rename_stmt.instance_of?(PgQuery::RenameStmt)
|
39
|
+
statement.stmt.rename_stmt.relation.relname
|
40
|
+
end
|
41
|
+
end
|
32
42
|
|
33
43
|
tables.uniq.count == 1
|
34
44
|
rescue PgQuery::ParseError
|
@@ -36,15 +46,20 @@ module PgOnlineSchemaChange
|
|
36
46
|
end
|
37
47
|
|
38
48
|
def table(query)
|
39
|
-
from_rename_statement =
|
40
|
-
|
41
|
-
|
49
|
+
from_rename_statement =
|
50
|
+
PgQuery
|
51
|
+
.parse(query)
|
52
|
+
.tree
|
53
|
+
.stmts
|
54
|
+
.filter_map { |statement| statement.stmt.rename_stmt&.relation&.relname }[
|
55
|
+
0
|
56
|
+
]
|
42
57
|
PgQuery.parse(query).tables[0] || from_rename_statement
|
43
58
|
end
|
44
59
|
|
45
60
|
def table_name(query, table)
|
46
61
|
table_name = "\"#{table}\""
|
47
|
-
if table =~ /[A-Z]/ &&
|
62
|
+
if table =~ /[A-Z]/ && query.include?(table_name) && table[0] != '"'
|
48
63
|
table_name
|
49
64
|
else
|
50
65
|
table
|
@@ -52,7 +67,9 @@ module PgOnlineSchemaChange
|
|
52
67
|
end
|
53
68
|
|
54
69
|
def run(connection, query, reuse_trasaction = false, &block)
|
55
|
-
|
70
|
+
if [PG::PQTRANS_INERROR, PG::PQTRANS_UNKNOWN].include?(connection.transaction_status)
|
71
|
+
connection.cancel
|
72
|
+
end
|
56
73
|
|
57
74
|
logger.debug("Running query", { query: query })
|
58
75
|
|
@@ -74,18 +91,19 @@ module PgOnlineSchemaChange
|
|
74
91
|
def table_columns(client, table = nil, reuse_trasaction = false)
|
75
92
|
sql = <<~SQL
|
76
93
|
SELECT attname as column_name, format_type(atttypid, atttypmod) as type, attnum as column_position FROM pg_attribute
|
77
|
-
WHERE attrelid =
|
94
|
+
WHERE attrelid = '#{table || client.table_name}'::regclass AND attnum > 0 AND NOT attisdropped
|
78
95
|
ORDER BY attnum;
|
79
96
|
SQL
|
80
97
|
mapped_columns = []
|
81
98
|
|
82
99
|
run(client.connection, sql, reuse_trasaction) do |result|
|
83
|
-
mapped_columns =
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
100
|
+
mapped_columns =
|
101
|
+
result.map do |row|
|
102
|
+
row["column_name_regular"] = row["column_name"]
|
103
|
+
row["column_name"] = client.connection.quote_ident(row["column_name"])
|
104
|
+
row["column_position"] = row["column_position"].to_i
|
105
|
+
row
|
106
|
+
end
|
89
107
|
end
|
90
108
|
|
91
109
|
mapped_columns
|
@@ -95,7 +113,9 @@ module PgOnlineSchemaChange
|
|
95
113
|
parsed_query = PgQuery.parse(client.alter_statement)
|
96
114
|
|
97
115
|
parsed_query.tree.stmts.each do |statement|
|
98
|
-
|
116
|
+
if statement.stmt.alter_table_stmt
|
117
|
+
statement.stmt.alter_table_stmt.relation.relname = shadow_table
|
118
|
+
end
|
99
119
|
|
100
120
|
statement.stmt.rename_stmt.relation.relname = shadow_table if statement.stmt.rename_stmt
|
101
121
|
end
|
@@ -106,13 +126,11 @@ module PgOnlineSchemaChange
|
|
106
126
|
query = <<~SQL
|
107
127
|
SELECT indexdef, schemaname
|
108
128
|
FROM pg_indexes
|
109
|
-
WHERE schemaname =
|
129
|
+
WHERE schemaname = '#{client.schema}' AND tablename = '#{table}'
|
110
130
|
SQL
|
111
131
|
|
112
132
|
indexes = []
|
113
|
-
run(client.connection, query)
|
114
|
-
indexes = result.map { |row| row["indexdef"] }
|
115
|
-
end
|
133
|
+
run(client.connection, query) { |result| indexes = result.map { |row| row["indexdef"] } }
|
116
134
|
|
117
135
|
indexes
|
118
136
|
end
|
@@ -120,13 +138,11 @@ module PgOnlineSchemaChange
|
|
120
138
|
def get_triggers_for(client, table)
|
121
139
|
query = <<~SQL
|
122
140
|
SELECT pg_get_triggerdef(oid) as tdef FROM pg_trigger
|
123
|
-
WHERE tgrelid =
|
141
|
+
WHERE tgrelid = '#{client.schema}.#{table}'::regclass AND tgisinternal = FALSE;
|
124
142
|
SQL
|
125
143
|
|
126
144
|
triggers = []
|
127
|
-
run(client.connection, query)
|
128
|
-
triggers = result.map { |row| "#{row["tdef"]};" }
|
129
|
-
end
|
145
|
+
run(client.connection, query) { |result| triggers = result.map { |row| "#{row["tdef"]};" } }
|
130
146
|
|
131
147
|
triggers.join(";")
|
132
148
|
end
|
@@ -144,9 +160,7 @@ module PgOnlineSchemaChange
|
|
144
160
|
SQL
|
145
161
|
|
146
162
|
constraints = []
|
147
|
-
run(client.connection, query)
|
148
|
-
constraints = result.map { |row| row }
|
149
|
-
end
|
163
|
+
run(client.connection, query) { |result| constraints = result.map { |row| row } }
|
150
164
|
|
151
165
|
constraints
|
152
166
|
end
|
@@ -164,72 +178,93 @@ module PgOnlineSchemaChange
|
|
164
178
|
end
|
165
179
|
|
166
180
|
def referential_foreign_keys_to_refresh(client, table)
|
167
|
-
references =
|
168
|
-
|
169
|
-
|
181
|
+
references =
|
182
|
+
get_all_constraints_for(client).select do |row|
|
183
|
+
row["table_from"] == table && row["constraint_type"] == "f"
|
184
|
+
end
|
170
185
|
|
171
|
-
references
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
186
|
+
references
|
187
|
+
.map do |row|
|
188
|
+
add_statement =
|
189
|
+
if row["definition"].end_with?("NOT VALID")
|
190
|
+
"ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]};"
|
191
|
+
else
|
192
|
+
"ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]} NOT VALID;"
|
193
|
+
end
|
177
194
|
|
178
|
-
|
195
|
+
drop_statement =
|
196
|
+
"ALTER TABLE #{row["table_on"]} DROP CONSTRAINT #{row["constraint_name"]};"
|
179
197
|
|
180
|
-
|
181
|
-
|
198
|
+
"#{drop_statement} #{add_statement}"
|
199
|
+
end
|
200
|
+
.join
|
182
201
|
end
|
183
202
|
|
184
203
|
def self_foreign_keys_to_refresh(client, table)
|
185
|
-
references =
|
186
|
-
|
187
|
-
|
204
|
+
references =
|
205
|
+
get_all_constraints_for(client).select do |row|
|
206
|
+
row["table_on"] == table && row["constraint_type"] == "f"
|
207
|
+
end
|
188
208
|
|
189
|
-
references
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
209
|
+
references
|
210
|
+
.map do |row|
|
211
|
+
add_statement =
|
212
|
+
if row["definition"].end_with?("NOT VALID")
|
213
|
+
"ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]};"
|
214
|
+
else
|
215
|
+
"ALTER TABLE #{row["table_on"]} ADD CONSTRAINT #{row["constraint_name"]} #{row["definition"]} NOT VALID;"
|
216
|
+
end
|
217
|
+
add_statement
|
218
|
+
end
|
219
|
+
.join
|
197
220
|
end
|
198
221
|
|
199
222
|
def get_foreign_keys_to_validate(client, table)
|
200
223
|
constraints = get_all_constraints_for(client)
|
201
|
-
referential_foreign_keys =
|
202
|
-
row["table_from"] == table && row["constraint_type"] == "f"
|
203
|
-
end
|
224
|
+
referential_foreign_keys =
|
225
|
+
constraints.select { |row| row["table_from"] == table && row["constraint_type"] == "f" }
|
204
226
|
|
205
|
-
self_foreign_keys =
|
206
|
-
row["table_on"] == table && row["constraint_type"] == "f"
|
207
|
-
end
|
227
|
+
self_foreign_keys =
|
228
|
+
constraints.select { |row| row["table_on"] == table && row["constraint_type"] == "f" }
|
208
229
|
|
209
|
-
[referential_foreign_keys, self_foreign_keys].flatten
|
210
|
-
|
211
|
-
|
230
|
+
[referential_foreign_keys, self_foreign_keys].flatten
|
231
|
+
.map do |row|
|
232
|
+
"ALTER TABLE #{row["table_on"]} VALIDATE CONSTRAINT #{row["constraint_name"]};"
|
233
|
+
end
|
234
|
+
.join
|
212
235
|
end
|
213
236
|
|
214
237
|
def dropped_columns(client)
|
215
|
-
PgQuery
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
238
|
+
PgQuery
|
239
|
+
.parse(client.alter_statement)
|
240
|
+
.tree
|
241
|
+
.stmts
|
242
|
+
.map do |statement|
|
243
|
+
next if statement.stmt.alter_table_stmt.nil?
|
244
|
+
|
245
|
+
statement.stmt.alter_table_stmt.cmds.map do |cmd|
|
246
|
+
cmd.alter_table_cmd.name if cmd.alter_table_cmd.subtype == DROPPED_COLUMN_TYPE
|
247
|
+
end
|
220
248
|
end
|
221
|
-
|
249
|
+
.flatten
|
250
|
+
.compact
|
222
251
|
end
|
223
252
|
|
224
253
|
def renamed_columns(client)
|
225
|
-
PgQuery
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
254
|
+
PgQuery
|
255
|
+
.parse(client.alter_statement)
|
256
|
+
.tree
|
257
|
+
.stmts
|
258
|
+
.map do |statement|
|
259
|
+
next if statement.stmt.rename_stmt.nil?
|
260
|
+
|
261
|
+
{
|
262
|
+
old_name: statement.stmt.rename_stmt.subname,
|
263
|
+
new_name: statement.stmt.rename_stmt.newname,
|
264
|
+
}
|
265
|
+
end
|
266
|
+
.flatten
|
267
|
+
.compact
|
233
268
|
end
|
234
269
|
|
235
270
|
def primary_key_for(client, table)
|
@@ -238,9 +273,9 @@ module PgOnlineSchemaChange
|
|
238
273
|
pg_attribute.attname as column_name
|
239
274
|
FROM pg_index, pg_class, pg_attribute, pg_namespace
|
240
275
|
WHERE
|
241
|
-
pg_class.oid =
|
276
|
+
pg_class.oid = '#{table}'::regclass AND
|
242
277
|
indrelid = pg_class.oid AND
|
243
|
-
nspname =
|
278
|
+
nspname = '#{client.schema}' AND
|
244
279
|
pg_class.relnamespace = pg_namespace.oid AND
|
245
280
|
pg_attribute.attrelid = pg_class.oid AND
|
246
281
|
pg_attribute.attnum = any(pg_index.indkey)
|
@@ -248,16 +283,14 @@ module PgOnlineSchemaChange
|
|
248
283
|
SQL
|
249
284
|
|
250
285
|
columns = []
|
251
|
-
run(client.connection, query)
|
252
|
-
columns = result.map { |row| row["column_name"] }
|
253
|
-
end
|
286
|
+
run(client.connection, query) { |result| columns = result.map { |row| row["column_name"] } }
|
254
287
|
|
255
288
|
columns.first
|
256
289
|
end
|
257
290
|
|
258
291
|
def storage_parameters_for(client, table, reuse_trasaction = false)
|
259
292
|
query = <<~SQL
|
260
|
-
SELECT array_to_string(reloptions, ',') as params FROM pg_class WHERE relname
|
293
|
+
SELECT array_to_string(reloptions, ',') as params FROM pg_class WHERE relname='#{table}';
|
261
294
|
SQL
|
262
295
|
|
263
296
|
columns = []
|
@@ -305,16 +338,17 @@ module PgOnlineSchemaChange
|
|
305
338
|
logger.info("Terminating other backends")
|
306
339
|
|
307
340
|
query = <<~SQL
|
308
|
-
SELECT pg_terminate_backend(pid) FROM pg_locks WHERE locktype = 'relation' AND relation =
|
341
|
+
SELECT pg_terminate_backend(pid) FROM pg_locks WHERE locktype = 'relation' AND relation = '#{table}'::regclass::oid AND pid <> pg_backend_pid()
|
309
342
|
SQL
|
310
343
|
|
311
344
|
run(client.connection, query, true)
|
312
345
|
end
|
313
346
|
|
314
347
|
def copy_data_statement(client, shadow_table, reuse_trasaction = false)
|
315
|
-
select_columns =
|
316
|
-
entry
|
317
|
-
|
348
|
+
select_columns =
|
349
|
+
table_columns(client, client.table_name, reuse_trasaction).map do |entry|
|
350
|
+
entry["column_name_regular"]
|
351
|
+
end
|
318
352
|
|
319
353
|
select_columns -= dropped_columns_list if dropped_columns_list.any?
|
320
354
|
|
@@ -332,9 +366,7 @@ module PgOnlineSchemaChange
|
|
332
366
|
client.connection.quote_ident(insert_into_column)
|
333
367
|
end
|
334
368
|
|
335
|
-
select_columns.map!
|
336
|
-
client.connection.quote_ident(select_column)
|
337
|
-
end
|
369
|
+
select_columns.map! { |select_column| client.connection.quote_ident(select_column) }
|
338
370
|
|
339
371
|
<<~SQL
|
340
372
|
INSERT INTO #{shadow_table}(#{insert_into_columns.join(", ")})
|
@@ -345,14 +377,12 @@ module PgOnlineSchemaChange
|
|
345
377
|
|
346
378
|
def primary_key_sequence(shadow_table, primary_key, opened)
|
347
379
|
query = <<~SQL
|
348
|
-
SELECT pg_get_serial_sequence(
|
380
|
+
SELECT pg_get_serial_sequence('#{shadow_table}', '#{primary_key}') as sequence_name
|
349
381
|
SQL
|
350
382
|
|
351
383
|
result = run(client.connection, query, opened)
|
352
384
|
|
353
|
-
result.map
|
354
|
-
row["sequence_name"]
|
355
|
-
end&.first
|
385
|
+
result.map { |row| row["sequence_name"] }&.first
|
356
386
|
end
|
357
387
|
|
358
388
|
def query_for_primary_key_refresh(shadow_table, primary_key, table, opened)
|
@@ -361,7 +391,7 @@ module PgOnlineSchemaChange
|
|
361
391
|
return "" if sequence_name.nil?
|
362
392
|
|
363
393
|
<<~SQL
|
364
|
-
SELECT setval((select pg_get_serial_sequence(
|
394
|
+
SELECT setval((select pg_get_serial_sequence('#{shadow_table}', '#{primary_key}')), (SELECT max(#{primary_key}) FROM #{table}));
|
365
395
|
SQL
|
366
396
|
end
|
367
397
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
4
|
-
|
5
3
|
module PgOnlineSchemaChange
|
6
4
|
class Replay
|
7
5
|
extend Helper
|
@@ -29,7 +27,9 @@ module PgOnlineSchemaChange
|
|
29
27
|
SQL
|
30
28
|
|
31
29
|
rows = []
|
32
|
-
Query.run(client.connection, select_query, reuse_trasaction)
|
30
|
+
Query.run(client.connection, select_query, reuse_trasaction) do |result|
|
31
|
+
rows = result.map { |row| row }
|
32
|
+
end
|
33
33
|
|
34
34
|
rows
|
35
35
|
end
|
@@ -48,14 +48,10 @@ module PgOnlineSchemaChange
|
|
48
48
|
|
49
49
|
# Remove audit table cols, since we will be
|
50
50
|
# re-mapping them for inserts and updates
|
51
|
-
reserved_columns.each
|
52
|
-
new_row.delete(col)
|
53
|
-
end
|
51
|
+
reserved_columns.each { |col| new_row.delete(col) }
|
54
52
|
|
55
53
|
if dropped_columns_list.any?
|
56
|
-
dropped_columns_list.each
|
57
|
-
new_row.delete(dropped_column)
|
58
|
-
end
|
54
|
+
dropped_columns_list.each { |dropped_column| new_row.delete(dropped_column) }
|
59
55
|
end
|
60
56
|
|
61
57
|
if renamed_columns_list.any?
|
@@ -69,13 +65,9 @@ module PgOnlineSchemaChange
|
|
69
65
|
|
70
66
|
# quote indent column to preserve case insensitivity
|
71
67
|
# ensure rows are escaped
|
72
|
-
new_row = new_row.transform_keys
|
73
|
-
client.connection.quote_ident(column)
|
74
|
-
end
|
68
|
+
new_row = new_row.transform_keys { |column| client.connection.quote_ident(column) }
|
75
69
|
|
76
|
-
new_row = new_row.transform_values
|
77
|
-
client.connection.escape_string(value)
|
78
|
-
end
|
70
|
+
new_row = new_row.transform_values { |value| client.connection.escape_string(value) }
|
79
71
|
|
80
72
|
case row[operation_type_column]
|
81
73
|
when "INSERT"
|
@@ -89,21 +81,19 @@ module PgOnlineSchemaChange
|
|
89
81
|
|
90
82
|
to_be_deleted_rows << "'#{row[audit_table_pk]}'"
|
91
83
|
when "UPDATE"
|
92
|
-
set_values = new_row.map
|
93
|
-
"#{column} = '#{value}'"
|
94
|
-
end.join(",")
|
84
|
+
set_values = new_row.map { |column, value| "#{column} = '#{value}'" }.join(",")
|
95
85
|
|
96
86
|
sql = <<~SQL
|
97
87
|
UPDATE #{shadow_table}
|
98
88
|
SET #{set_values}
|
99
|
-
WHERE #{primary_key}
|
89
|
+
WHERE #{primary_key}='#{row[primary_key]}';
|
100
90
|
SQL
|
101
91
|
to_be_replayed << sql
|
102
92
|
|
103
93
|
to_be_deleted_rows << "'#{row[audit_table_pk]}'"
|
104
94
|
when "DELETE"
|
105
95
|
sql = <<~SQL
|
106
|
-
DELETE FROM #{shadow_table} WHERE #{primary_key}
|
96
|
+
DELETE FROM #{shadow_table} WHERE #{primary_key}='#{row[primary_key]}';
|
107
97
|
SQL
|
108
98
|
to_be_replayed << sql
|
109
99
|
|