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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/exwiw/adapter/postgresql_adapter.rb +92 -11
- data/lib/exwiw/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 568620ee295b1822c2606a42be450bbe5601aabb874606b6773b72619261cbba
|
|
4
|
+
data.tar.gz: 46f7b6f97c6b77b7046dfb19117734286ecb5d88001214f9d6e1995560f0b50a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
181
|
-
|
|
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) }
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
"
|
|
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