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