exwiw 0.3.9 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea6a0b4fda446d66766027139136320937a59319bfe28d83ee595cf062a4023a
4
- data.tar.gz: 1ce3d4a7601ae6b3f1fe6ca8ed22e977389ac7ab46973c33113206911893326e
3
+ metadata.gz: 568620ee295b1822c2606a42be450bbe5601aabb874606b6773b72619261cbba
4
+ data.tar.gz: 46f7b6f97c6b77b7046dfb19117734286ecb5d88001214f9d6e1995560f0b50a
5
5
  SHA512:
6
- metadata.gz: '08ffcb1713110629652b4047b159613ab3d13e5fffcd092433282f28564328d1609572d8f159b1c0e7fddfab1be4fc92651b8f819ddfa44a913e18da04e4aeef'
7
- data.tar.gz: 77529a1897ded0943898f60d8a55d54bfdf35f80fc512b57be7300adad9fc8c99767199de8e6d9fb9649dc2644fbcfc2252e445de2f5c38bc20aece5ae4a56d5
6
+ metadata.gz: 8800c7adfa78ea01fb513eb3dbbe2b3fbfa98b13916d5b4e5a205232851b73e1bbef8cd9d0190abe5a9375d073c753d2f92be583c3a2a96d898cb757cd883a0a
7
+ data.tar.gz: 02f0ebd17ed423dfffbba3aefe2fbba01b533c23672d680ccce7c21edf2b36dbfc0c66ee72b8f4433af3af643aaea126861233a99d2222a2d347f407c8ab5c27
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.0] - 2026-06-04
6
+
7
+ ### Fixed
8
+
9
+ - PostgreSQL: extraction no longer fails with `operator does not exist: character varying = uuid` when a `belongs_to` chain crosses a varchar foreign key and a uuid primary key. The adapter now introspects column types via `pg_attribute`/`pg_type` at query compile time and injects `::text` casts on both sides of the comparison when a uuid/varchar mismatch is detected. Covers JOIN ON conditions, WHERE IN subqueries, and bulk-delete IN clauses. ([#73](https://github.com/heyinc/exwiw/pull/73))
10
+
5
11
  ## [0.3.9] - 2026-06-03
6
12
 
7
13
  ### Added
@@ -177,8 +177,17 @@ module Exwiw
177
177
  end
178
178
 
179
179
  foreign_key = first_join.foreign_key
180
- subquery_sql = compile_ast(subquery_ast)
181
- sql += "\nWHERE #{select_query_ast.from_table_name}.#{foreign_key} IN (#{subquery_sql})"
180
+ outer_table = select_query_ast.from_table_name
181
+ inner_table = first_join.join_table_name
182
+ inner_column = first_join.primary_key
183
+ cast_to = types_need_cast?(
184
+ column_pg_type(outer_table, foreign_key),
185
+ column_pg_type(inner_table, inner_column)
186
+ ) ? 'text' : nil
187
+ subquery_sql = compile_ast(subquery_ast, select_cast_to: cast_to)
188
+ outer_expr = "#{outer_table}.#{foreign_key}"
189
+ outer_expr = "#{outer_expr}::text" if cast_to
190
+ sql += "\nWHERE #{outer_expr} IN (#{subquery_sql})"
182
191
 
183
192
  # first_join.base_where_clauses holds conditions on the outer
184
193
  # delete-target table (from_table_name), such as a polymorphic type
@@ -195,19 +204,30 @@ module Exwiw
195
204
  sql
196
205
  end
197
206
 
198
- def compile_ast(query_ast)
207
+ def compile_ast(query_ast, select_cast_to: nil)
199
208
  raise NotImplementedError unless query_ast.is_a?(Exwiw::QueryAst::Select)
200
209
 
201
210
  sql = "SELECT "
202
211
  sql += if query_ast.select_all
203
212
  "*"
204
213
  else
205
- query_ast.columns.map { |col| compile_column_name(query_ast, col) }.join(', ')
214
+ cols = query_ast.columns.map { |col| compile_column_name(query_ast, col) }
215
+ cols = cols.map { |c| "#{c}::#{select_cast_to}" } if select_cast_to
216
+ cols.join(', ')
206
217
  end
207
218
  sql += " FROM #{query_ast.from_table_name}"
208
219
 
209
220
  query_ast.join_clauses.each do |join|
210
- sql += " JOIN #{join.join_table_name} ON #{join.base_table_name}.#{join.foreign_key} = #{join.join_table_name}.#{join.primary_key}"
221
+ fk_expr = "#{join.base_table_name}.#{join.foreign_key}"
222
+ pk_expr = "#{join.join_table_name}.#{join.primary_key}"
223
+ if types_need_cast?(
224
+ column_pg_type(join.base_table_name, join.foreign_key),
225
+ column_pg_type(join.join_table_name, join.primary_key)
226
+ )
227
+ fk_expr = "#{fk_expr}::text"
228
+ pk_expr = "#{pk_expr}::text"
229
+ end
230
+ sql += " JOIN #{join.join_table_name} ON #{fk_expr} = #{pk_expr}"
211
231
 
212
232
  join.where_clauses.each do |where|
213
233
  compiled_where_condition = compile_where_condition(where, join.join_table_name)
@@ -246,23 +266,54 @@ module Exwiw
246
266
  "#{key} IN (#{values.join(', ')})"
247
267
  end
248
268
  elsif where_clause.operator == :in_subquery
249
- "#{key} IN (#{compile_subquery(where_clause.value)})"
269
+ subquery_sql = compile_subquery(where_clause.value, outer_table: table_name, outer_column: where_clause.column_name)
270
+ cast_to = subquery_cast_to(where_clause.value, table_name, where_clause.column_name)
271
+ outer_key = cast_to ? "#{key}::#{cast_to}" : key
272
+ "#{outer_key} IN (#{subquery_sql})"
250
273
  else
251
274
  raise "Unsupported operator: #{where_clause.operator}"
252
275
  end
253
276
  end
254
277
 
255
- private def compile_subquery(subquery)
256
- # A SelectSubquery wraps a full Select (the referencing table's
257
- # extraction query, projected to a foreign key); compile it as-is.
258
- return compile_ast(subquery.query) if subquery.is_a?(Exwiw::QueryAst::SelectSubquery)
278
+ private def compile_subquery(subquery, outer_table: nil, outer_column: nil)
279
+ cast_to = subquery_cast_to(subquery, outer_table, outer_column)
280
+
281
+ if subquery.is_a?(Exwiw::QueryAst::SelectSubquery)
282
+ return compile_ast(subquery.query, select_cast_to: cast_to)
283
+ end
259
284
 
260
285
  inner_values = subquery.where_values.map { |v| escape_value(v) }
261
- "SELECT #{subquery.table_name}.#{subquery.select_column} " \
286
+ select_expr = "#{subquery.table_name}.#{subquery.select_column}"
287
+ select_expr = "#{select_expr}::#{cast_to}" if cast_to
288
+ "SELECT #{select_expr} " \
262
289
  "FROM #{subquery.table_name} " \
263
290
  "WHERE #{subquery.table_name}.#{subquery.where_column} IN (#{inner_values.join(', ')})"
264
291
  end
265
292
 
293
+ private def subquery_select_target(subquery)
294
+ case subquery
295
+ when Exwiw::QueryAst::SelectSubquery
296
+ q = subquery.query
297
+ col = q.columns.first
298
+ col ? [q.from_table_name, col.name] : [nil, nil]
299
+ when Exwiw::QueryAst::Subquery
300
+ [subquery.table_name, subquery.select_column]
301
+ else
302
+ [nil, nil]
303
+ end
304
+ end
305
+
306
+ private def subquery_cast_to(subquery, outer_table, outer_column)
307
+ return nil if outer_table.nil? || outer_column.nil?
308
+
309
+ inner_table, inner_column = subquery_select_target(subquery)
310
+ return nil if inner_table.nil?
311
+
312
+ outer_type = column_pg_type(outer_table, outer_column)
313
+ inner_type = column_pg_type(inner_table, inner_column)
314
+ types_need_cast?(outer_type, inner_type) ? 'text' : nil
315
+ end
316
+
266
317
  private def escape_value(value)
267
318
  case value
268
319
  when nil
@@ -351,6 +402,36 @@ module Exwiw
351
402
  end
352
403
  end
353
404
 
405
+ private def column_pg_type(table_name, column_name)
406
+ @column_type_cache ||= {}
407
+ cache_key = [table_name, column_name]
408
+ return @column_type_cache[cache_key] if @column_type_cache.key?(cache_key)
409
+
410
+ sql = <<~SQL
411
+ SELECT t.typname
412
+ FROM pg_attribute a
413
+ JOIN pg_class c ON c.oid = a.attrelid
414
+ JOIN pg_type t ON t.oid = a.atttypid
415
+ WHERE c.relname = $1
416
+ AND a.attname = $2
417
+ AND a.attnum > 0
418
+ AND NOT a.attisdropped
419
+ LIMIT 1
420
+ SQL
421
+
422
+ result = connection.exec_params(sql, [table_name, column_name])
423
+ @column_type_cache[cache_key] = result.ntuples > 0 ? result.getvalue(0, 0) : nil
424
+ end
425
+
426
+ private def types_need_cast?(type_a, type_b)
427
+ return false if type_a.nil? || type_b.nil?
428
+ return false if type_a == type_b
429
+
430
+ string_types = %w[varchar text bpchar name].freeze
431
+ (type_a == 'uuid' && string_types.include?(type_b)) ||
432
+ (type_b == 'uuid' && string_types.include?(type_a))
433
+ end
434
+
354
435
  private def connection
355
436
  @connection ||=
356
437
  begin
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.3.9"
4
+ VERSION = "0.4.0"
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.3.9
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shia