exwiw 0.2.2 → 0.2.4

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: 369701f0b142d0660d988630013e5f421c1d4ad817a834615e25177721700abc
4
- data.tar.gz: 950edc07255a1520bfa9bae067c028160219790ecc84379210e6532054de0ddb
3
+ metadata.gz: 95749db3a2093fe305a67c6caacf1b6861f791d99f70283a7fa63ce765b9f846
4
+ data.tar.gz: '087a7260b7d5cd3809fc72c8a783f408e87091e1e6bc816eab4a965e489b4404'
5
5
  SHA512:
6
- metadata.gz: 852cd30d5121dbbbf8d7abeb1fd486c188c4ab177339620d2129ebfed10ef1dfe13a928c34840f082c295e255d0a15e3a72cf1e71151292dce50657bbf125ac5
7
- data.tar.gz: a57d914dd5794caa00df4323ab84df3c9fcdec5694c0e32fde68d44cc7f78369b3d8359614c55a35608eb2b9d7202af2078ffc1ee0a3a566d69cf1eb5141de72
6
+ metadata.gz: cafb2801b708bf26124f12f6a13799f35d6b5f17ec2977fc49464ae56bb6482434bf9d1a155b176968d4f7b545f172a1fc56c3114958206da0605c426194aebf
7
+ data.tar.gz: c9c05f6da15c12ee45963d0552122310cda5a3261f210d2dbb069986751b9d245bad9661ef9ca5d8ed7fbec04afbe00e637bc9b852d0d15b0eea4429db4c6ec2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.4] - 2026-05-28
6
+
7
+ ### Added
8
+
9
+ - Dump/explain queries issued against the source DB now carry an identifying comment (e.g. `/* exwiw table=shops */`) so exwiw-originated queries can be spotted in `processlist` / slow query log / `db.currentOp()` and killed by table when they run long. Added to the SELECT/EXPLAIN of the `mysql2`, `postgresql`, and `sqlite3` adapters and to MongoDB `find` (via `.comment(...)`); the `explain` subcommand's printed SQL matches the emitted form. The comment is applied only at the query-issuing boundary, so generated `insert-*` / `delete-*` files (and `to_bulk_delete` subqueries) stay comment-free, and the version is intentionally omitted to keep snapshots/diffs stable. ([#35](https://github.com/heyinc/exwiw/pull/35))
10
+
11
+ ## [0.2.3] - 2026-05-27
12
+
13
+ ### Fixed
14
+
15
+ - PostgreSQL: schema dump now includes `CREATE TYPE ... AS ENUM` for custom enum types referenced by dumped tables. Previously `pg_dump --table` excluded schema-level type definitions, causing `type "..." does not exist` errors on import. ([#22](https://github.com/heyinc/exwiw/pull/22))
16
+
5
17
  ## [0.2.2] - 2026-05-26
6
18
 
7
19
  ### Changed
data/README.md CHANGED
@@ -347,7 +347,12 @@ At runtime:
347
347
 
348
348
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
349
349
 
350
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
350
+ To install this gem onto your local machine, run `bundle exec rake install`.
351
+
352
+ To release a new version:
353
+
354
+ 1. Run the **Release PR** workflow from the Actions tab with the new version number (e.g. `0.2.3`). This creates a PR that bumps `version.rb` and `CHANGELOG.md`.
355
+ 2. Merge the PR. The **Release** workflow runs automatically, creating a git tag and publishing the gem to [rubygems.org](https://rubygems.org).
351
356
 
352
357
  ## Contributing
353
358
 
@@ -73,7 +73,11 @@ module Exwiw
73
73
  def execute(query)
74
74
  @logger.debug(" Executing Mongo find on '#{query.collection}': filter=#{query.filter.inspect} projection=#{query.projection.inspect}")
75
75
 
76
- docs = db[query.collection].find(query.filter).projection(query.projection).to_a
76
+ docs = db[query.collection]
77
+ .find(query.filter)
78
+ .projection(query.projection)
79
+ .comment(query_comment_text("collection=#{query.collection}"))
80
+ .to_a
77
81
 
78
82
  @state[query.collection] = docs.map { |doc| doc[query.primary_key] }
79
83
 
@@ -8,14 +8,14 @@ module Exwiw
8
8
  end
9
9
 
10
10
  def execute(query_ast)
11
- sql = compile_ast(query_ast)
11
+ sql = commented_sql(query_ast)
12
12
 
13
13
  @logger.debug(" Executing SQL: \n#{sql}")
14
14
  connection.query(sql, cast: false, as: :array).to_a
15
15
  end
16
16
 
17
17
  def explain(query_ast)
18
- sql = compile_ast(query_ast)
18
+ sql = commented_sql(query_ast)
19
19
 
20
20
  @logger.debug(" Executing EXPLAIN: \n#{sql}")
21
21
  rows = connection.query("EXPLAIN #{sql}", cast: false).to_a
@@ -8,14 +8,14 @@ module Exwiw
8
8
  end
9
9
 
10
10
  def execute(query_ast)
11
- sql = compile_ast(query_ast)
11
+ sql = commented_sql(query_ast)
12
12
 
13
13
  @logger.debug(" Executing SQL: \n#{sql}")
14
14
  connection.exec(sql).values
15
15
  end
16
16
 
17
17
  def explain(query_ast)
18
- sql = compile_ast(query_ast)
18
+ sql = commented_sql(query_ast)
19
19
 
20
20
  @logger.debug(" Executing EXPLAIN: \n#{sql}")
21
21
  connection.exec("EXPLAIN #{sql}").values.map(&:first).join("\n")
@@ -52,6 +52,13 @@ module Exwiw
52
52
  raise "pg_dump failed (exit #{status.exitstatus}): #{stderr}"
53
53
  end
54
54
 
55
+ enum_types = query_enum_types(table_names)
56
+ unless enum_types.empty?
57
+ enum_ddl = DdlPostprocessor.create_type_enum_statements(enum_types)
58
+ @logger.debug(" Found #{enum_types.size} enum type(s) to prepend.")
59
+ stdout = enum_ddl + stdout
60
+ end
61
+
55
62
  idempotent = stdout
56
63
  idempotent = DdlPostprocessor.add_if_not_exists_to_create_schema(idempotent)
57
64
  idempotent = DdlPostprocessor.add_if_not_exists_to_create_sequence(idempotent)
@@ -264,6 +271,40 @@ module Exwiw
264
271
  end
265
272
  end
266
273
 
274
+ private def query_enum_types(table_names)
275
+ return [] if table_names.empty?
276
+
277
+ placeholders = table_names.each_with_index.map { |_, i| "$#{i + 1}" }.join(', ')
278
+ sql = <<~SQL
279
+ SELECT DISTINCT
280
+ n.nspname AS type_schema,
281
+ t.typname AS type_name,
282
+ array_agg(e.enumlabel ORDER BY e.enumsortorder) AS enum_labels
283
+ FROM pg_attribute a
284
+ JOIN pg_class c ON c.oid = a.attrelid
285
+ JOIN pg_namespace cn ON cn.oid = c.relnamespace
286
+ JOIN pg_type t ON t.oid = a.atttypid
287
+ JOIN pg_namespace n ON n.oid = t.typnamespace
288
+ JOIN pg_enum e ON e.enumtypid = t.oid
289
+ WHERE c.relname IN (#{placeholders})
290
+ AND a.attnum > 0
291
+ AND NOT a.attisdropped
292
+ AND t.typtype = 'e'
293
+ GROUP BY n.nspname, t.typname
294
+ ORDER BY n.nspname, t.typname
295
+ SQL
296
+
297
+ result = connection.exec_params(sql, table_names)
298
+ decoder = PG::TextDecoder::Array.new
299
+ result.map do |row|
300
+ {
301
+ schema: row['type_schema'],
302
+ name: row['type_name'],
303
+ labels: decoder.decode(row['enum_labels']),
304
+ }
305
+ end
306
+ end
307
+
267
308
  private def connection
268
309
  @connection ||=
269
310
  begin
@@ -8,14 +8,14 @@ module Exwiw
8
8
  end
9
9
 
10
10
  def execute(query_ast)
11
- sql = compile_ast(query_ast)
11
+ sql = commented_sql(query_ast)
12
12
 
13
13
  @logger.debug(" Executing SQL: \n#{sql}")
14
14
  connection.execute(sql)
15
15
  end
16
16
 
17
17
  def explain(query_ast)
18
- sql = compile_ast(query_ast)
18
+ sql = commented_sql(query_ast)
19
19
 
20
20
  @logger.debug(" Executing EXPLAIN QUERY PLAN: \n#{sql}")
21
21
  rows = connection.execute("EXPLAIN QUERY PLAN #{sql}")
data/lib/exwiw/adapter.rb CHANGED
@@ -81,6 +81,34 @@ module Exwiw
81
81
  def explain(_query_ast)
82
82
  raise NotImplementedError, "#{self.class.name} does not implement #explain"
83
83
  end
84
+
85
+ # Identifier text prepended to every query exwiw sends to the (often
86
+ # production) source DB, so the statement is recognizable in the
87
+ # processlist / slow-query log / db.currentOp() and can be killed if it
88
+ # runs long. e.g. "exwiw table=shops". `label` is "table=..." /
89
+ # "collection=...". The version is intentionally omitted to keep the
90
+ # comment stable across releases (snapshots / diffs). Strips `*/` to
91
+ # avoid breaking out of the comment.
92
+ def query_comment_text(label = nil)
93
+ parts = ["exwiw"]
94
+ parts << label if label
95
+ parts.join(' ').gsub('*/', '')
96
+ end
97
+
98
+ # SQL block-comment form, prefixed to SELECT / EXPLAIN.
99
+ def sql_query_comment(query_ast)
100
+ label =
101
+ if query_ast.respond_to?(:from_table_name) && query_ast.from_table_name
102
+ "table=#{query_ast.from_table_name}"
103
+ end
104
+ "/* #{query_comment_text(label)} */"
105
+ end
106
+
107
+ # Comment-prefixed SELECT. Relies on the SQL adapter's #compile_ast
108
+ # (dispatched to the subclass at runtime).
109
+ def commented_sql(query_ast)
110
+ "#{sql_query_comment(query_ast)} #{compile_ast(query_ast)}"
111
+ end
84
112
  end
85
113
 
86
114
  # @params [Exwiw::QueryAst] query_ast
@@ -57,5 +57,24 @@ module Exwiw
57
57
  SQL
58
58
  end
59
59
  end
60
+
61
+ # Generate idempotent CREATE TYPE ... AS ENUM statements.
62
+ # +enum_types+ is an Array of Hashes with keys :schema, :name, :labels.
63
+ def create_type_enum_statements(enum_types)
64
+ return "" if enum_types.empty?
65
+
66
+ stmts = enum_types.map do |t|
67
+ qualified_name = "\"#{t[:schema]}\".\"#{t[:name]}\""
68
+ labels_sql = t[:labels].map { |l| "'#{l.gsub("'", "''")}'" }.join(', ')
69
+ <<~SQL.chomp
70
+ DO $exwiw$ BEGIN
71
+ CREATE TYPE #{qualified_name} AS ENUM (#{labels_sql});
72
+ EXCEPTION WHEN duplicate_object THEN NULL;
73
+ END $exwiw$;
74
+ SQL
75
+ end
76
+
77
+ stmts.join("\n\n") + "\n\n"
78
+ end
60
79
  end
61
80
  end
@@ -40,7 +40,7 @@ module Exwiw
40
40
  @logger.debug("Explaining '#{table_name}'... (#{idx + 1}/#{total_size})")
41
41
 
42
42
  query_ast = adapter.build_query(table, @dump_target, table_by_name)
43
- sql = adapter.compile_ast(query_ast)
43
+ sql = adapter.commented_sql(query_ast)
44
44
  explain_text = adapter.explain(query_ast)
45
45
 
46
46
  @io.puts "-- [#{idx + 1}/#{total_size}] #{table_name}"
data/lib/exwiw/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Exwiw
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exwiw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia