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 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