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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a765e9fe8c466b43b05b45401a6f0bbfc1d6ec3825dbc030308e1c7701ee436
4
- data.tar.gz: 5cb07a199e35f6fe1ad2dfb3cad11dded9f6e4b519af2d67a08fad850fdd715d
3
+ metadata.gz: 973cb542595ffacbaa942636be7b366e018a2141a75c1fd73e38e65e263ddd39
4
+ data.tar.gz: abe5178f45a6c8a00c0b64a5cee543b7fc22e126d7a5744adefada83fa6ab844
5
5
  SHA512:
6
- metadata.gz: a39814cfedba956f26f083a5fbbeeeced80818b6273656761e146d244e245627033ffbf48c5b69dc1a5bb4d5c9e3c9e01f10ec9ddbf95a7da9b8e36af60ada6d
7
- data.tar.gz: 87e06bd100570d1a18e11ec279894a7a417713237ee4134bace9fc5f62acbffd35d31e668b3b88c5031841d816a9eb8fe30faa546baae5e3027f432bb6567ecc
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 2022-09-24 14:22:44 UTC using RuboCop version 1.23.0.
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: 266
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: 68
34
+ # Offense count: 69
30
35
  # Configuration parameters: CountAsOne.
31
36
  RSpec/ExampleLength:
32
37
  Max: 55
33
38
 
34
- # Offense count: 24
39
+ # Offense count: 25
35
40
  RSpec/MultipleExpectations:
36
- Max: 13
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)
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.6)
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.3
88
+ 2.3.24
data/docker-compose.yml CHANGED
@@ -6,4 +6,4 @@ services:
6
6
  - "5432:5432"
7
7
  environment:
8
8
  POSTGRES_USER: jamesbond
9
- POSTGRES_DB: postgres
9
+ POSTGRES_DB: postgres
@@ -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
@@ -6,7 +6,7 @@ module PgOnlineSchemaChange
6
6
  result = Store.get(:primary_key)
7
7
  return result if result
8
8
 
9
- Store.set(:primary_key, Query.primary_key_for(client, client.table))
9
+ Store.set(:primary_key, Query.primary_key_for(client, client.table_name))
10
10
  end
11
11
 
12
12
  def logger
@@ -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.table))
42
- Store.set(:self_foreign_key_statements, Query.self_foreign_keys_to_refresh(client, client.table))
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.table});
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.table)
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.table};
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.table}
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.table}', '#{shadow_table}');", true)
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.table}', '#{shadow_table}');", true)
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.table, true) || ""
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.table })
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.table })
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.table })
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.table} SET (#{primary_table_storage_parameters});"
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.table)
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.table, opened)
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.table} RENAME to #{old_primary_table};
246
- ALTER TABLE #{shadow_table} RENAME to #{client.table};
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.table};
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.table};")
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.table)
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.table};
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.table}\'::regclass AND attnum > 0 AND NOT attisdropped
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.table} IN ACCESS EXCLUSIVE MODE;
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.table, reuse_trasaction).map do |entry|
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.table}
342
+ FROM ONLY #{client.table_name}
320
343
  SQL
321
344
  end
322
345
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgOnlineSchemaChange
4
- VERSION = "0.7.4"
4
+ VERSION = "0.7.5"
5
5
  end
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
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: 2022-09-24 00:00:00.000000000 Z
11
+ date: 2023-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ougai