foobara-postgresql-crud-driver 0.0.3 → 0.0.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: 6846188257d6cad52ea962972ddd82f82d72162ea0a46df18983a92bb3c2b444
4
- data.tar.gz: 349cb1d585f814335f083ed9cb5105a42bb9e9b10a8a8e5f9fa6444b3670dcfd
3
+ metadata.gz: 9da1f2e3a8d5170f4badb5d1ec0eeb8b6f443da56ce17b4b97911767620ae099
4
+ data.tar.gz: b12f27eca628e62f38c7f8d2a1af415d1ab3c72006a18d17f983b4fbb03eea91
5
5
  SHA512:
6
- metadata.gz: 516fede04188fb07fdb5b62be6400cdaf507c3d7b1ffc961ec5045591f9257c1bbeca2967dd21a12a6e96484690b30344422509e7a4ea5dc5724afab426223d0
7
- data.tar.gz: 4cab91509e1450dceafe8bcd022f25f48af4d52906a0d9b5c4a86d75cb1520f5347bf96b4b3f76c09bbb9cdb053e9fd03d8009f77d931831db9e1cfb5c291efa
6
+ metadata.gz: d7d0d2d19bc3cd5d3c1625b7b4958b0f160f9c733bdac7adc3f39ec09010ddad3f7fe8ce02aa9a9d4c9c6a7bc8216a731c13d299209947cf7ebb8a327a35fdb6
7
+ data.tar.gz: 8f4c6fa9014c60caef166ecdff36a22e7d87543276d36604da42ec8f7e95ff421547256e1d4e58b02f7ac789c5e5070b3f5fe0b3ec500406a1780fec49426fc7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [0.0.5] - 2025-11-10
2
+
3
+ - Improve error messages for foobara type/pg column incompatibilities
4
+ - Validate strings used as integers represent valid integers
5
+
6
+ ## [0.0.4] - 2025-11-09
7
+
8
+ - Support a few more column types
9
+ - Make sure an entity -> entity association is cast properly
10
+
1
11
  ## [0.0.3] - 2025-10-21
2
12
 
3
13
  - Pass through options to EntityAttributesCrudDriver instead of disallowing them
@@ -4,6 +4,18 @@ module Foobara
4
4
  class NoSuchColumnOrTableError < StandardError; end
5
5
 
6
6
  class UnsupportedPgColumnTypeError < StandardError
7
+ attr_accessor :pg_type, :attribute_name, :entity_class
8
+
9
+ def initialize(pg_type, entity_class)
10
+ # :nocov:
11
+ super("Unsupported column type #{pg_type} on #{entity_class.entity_name}")
12
+ # :nocov:
13
+ end
14
+ end
15
+
16
+ class UnsupportedPgColumnTypeForAttributeError < StandardError
17
+ attr_accessor :pg_type, :attribute_name, :entity_class
18
+
7
19
  def initialize(pg_type, attribute_name, entity_class)
8
20
  # :nocov:
9
21
  super("Unsupported column type #{pg_type} for attribute #{attribute_name} on #{entity_class.entity_name}")
@@ -11,12 +23,29 @@ module Foobara
11
23
  end
12
24
  end
13
25
 
26
+ class ColumnTypeMismatchError < StandardError
27
+ attr_accessor :pg_type, :foobara_type, :entity_class, :attribute_name
28
+
29
+ def initialize(pg_type:, foobara_type:, entity_class:, attribute_name: nil)
30
+ # TODO: figure out a way to test this code path
31
+ # :nocov:
32
+ self.pg_type = pg_type
33
+ self.foobara_type = foobara_type
34
+ self.entity_class = entity_class
35
+ self.attribute_name = attribute_name
36
+
37
+ super("Column type mismatch between foobara #{foobara_type.type_symbol} " \
38
+ "and postgres #{pg_type} for #{entity_class.entity_name}.#{attribute_name}")
39
+ # :nocov:
40
+ end
41
+ end
42
+
14
43
  class << self
15
44
  def get_transaction_number
16
45
  @get_transaction_number ||= 0
17
46
  @get_transaction_number += 1
18
47
  if @get_transaction_number > 65_535
19
- # TODO: test this codepath somehow
48
+ # TODO: test this code path somehow
20
49
  # :nocov:
21
50
  @get_transaction_number = 1
22
51
  # :nocov:
@@ -225,6 +254,7 @@ module Foobara
225
254
  SQL
226
255
 
227
256
  raw_connection.exec(sql)
257
+
228
258
  find(record_id)
229
259
  end
230
260
 
@@ -267,9 +297,11 @@ module Foobara
267
297
  info = column_info[attribute_name.to_s]
268
298
 
269
299
  value = case info[:type]
270
- when "integer", "text", "timestamp without time zone"
300
+ when "integer", "bigint",
301
+ "text", "character varying",
302
+ "timestamp without time zone"
271
303
  value
272
- when "jsonb"
304
+ when "jsonb", "json"
273
305
  if value.nil?
274
306
  unless info[:is_nullable]
275
307
  # :nocov:
@@ -306,79 +338,20 @@ module Foobara
306
338
  # :nocov:
307
339
  end
308
340
 
309
- pg_type = info[:type]
310
341
  foobara_type = entity_class.model_type.element_types.element_types[attribute_name]
311
342
 
312
- value = if value.nil?
313
- if info[:is_nullable]
314
- "NULL"
315
- else
316
- # :nocov:
317
- raise "Unexpected nil value for #{attribute_name}"
318
- # :nocov:
319
- end
320
- elsif foobara_type.extends?(:number)
321
- case pg_type
322
- when "integer"
323
- value.to_i
324
- else
325
- # :nocov:
326
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
327
- # :nocov:
328
- end
329
- elsif foobara_type.extends?(:string) || foobara_type.extends?(:symbol)
330
- case pg_type
331
- when "text"
332
- "'#{PG::Connection.escape(value.to_s)}'"
333
- else
334
- # :nocov:
335
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
336
- # :nocov:
337
- end
338
- elsif foobara_type.extends?(:datetime)
339
- case pg_type
340
- when "timestamp without time zone"
341
- "'#{PG::Connection.escape(value.inspect)}'"
342
- else
343
- # :nocov:
344
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
345
- # :nocov:
346
- end
347
- elsif foobara_type.extends?(:model) || foobara_type.extends?(:attributes)
348
- case pg_type
349
- when "jsonb"
350
- "'#{PG::Connection.escape(JSON.fast_generate(value))}'"
351
- else
352
- # :nocov:
353
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
354
- # :nocov:
355
- end
356
- elsif foobara_type.extends?(:array)
357
- element_type = foobara_type.element_type
358
-
359
- if element_type.extends?(:detached_entity)
360
- case pg_type
361
- when "ARRAY"
362
- elements_type = ARRAY_ELEMENT_ENCODERS[info[:element_type]]
363
- array_string = PG::TextEncoder::Array.new(elements_type:).encode(value)
364
- escaped = PG::Connection.escape(array_string)
365
-
366
- "'#{escaped}'"
367
- else
368
- # :nocov:
369
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
370
- # :nocov:
371
- end
372
- else
373
- # :nocov:
374
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
375
- # :nocov:
376
- end
377
- else
378
- # :nocov:
379
- raise UnsupportedPgColumnTypeError.new(pg_type, attribute_name, entity_class)
380
- # :nocov:
381
- end
343
+ value = begin
344
+ check_type_compatibility!(foobara_type, info, attribute_name:)
345
+ pg_cast_value(value, foobara_type, info)
346
+ rescue UnsupportedPgColumnTypeError => e
347
+ # :nocov:
348
+ raise UnsupportedPgColumnTypeForAttributeError.new(
349
+ e.pg_type,
350
+ attribute_name,
351
+ entity_class
352
+ )
353
+ # :nocov:
354
+ end
382
355
 
383
356
  [PostgresqlCrudDriver.escape_identifier(attribute_name), value]
384
357
  end
@@ -406,6 +379,107 @@ module Foobara
406
379
  ]
407
380
  end
408
381
  end
382
+
383
+ def pg_cast_value(value, foobara_type, pg_info)
384
+ pg_type = pg_info[:type]
385
+
386
+ if value.nil?
387
+ if pg_info[:is_nullable]
388
+ "NULL"
389
+ else
390
+ # :nocov:
391
+ raise "Unexpected nil value for #{attribute_name}"
392
+ # :nocov:
393
+ end
394
+ elsif foobara_type.extends?(:number)
395
+ validate_intable!(value)
396
+ value.to_i
397
+ elsif foobara_type.extends?(:string) || foobara_type.extends?(:symbol)
398
+ "'#{PG::Connection.escape(value.to_s)}'"
399
+ elsif foobara_type.extends?(:datetime)
400
+ "'#{PG::Connection.escape(value.inspect)}'"
401
+ elsif foobara_type.extends?(:detached_entity)
402
+ pg_cast_value(value, foobara_type.target_class.primary_key_type, pg_info)
403
+ elsif foobara_type.extends?(:model) || foobara_type.extends?(:attributes) ||
404
+ foobara_type == BuiltinTypes[:duck]
405
+ "'#{PG::Connection.escape(JSON.fast_generate(value))}'"
406
+ elsif foobara_type.extends?(:array)
407
+ element_type = foobara_type.element_type
408
+
409
+ if element_type.extends?(:detached_entity)
410
+ if element_type.target_class.primary_key_type.extends?(:integer)
411
+ value.each { validate_intable!(it) }
412
+ end
413
+ elements_type = ARRAY_ELEMENT_ENCODERS[pg_info[:element_type]]
414
+ array_string = PG::TextEncoder::Array.new(elements_type:).encode(value)
415
+ escaped = PG::Connection.escape(array_string)
416
+
417
+ "'#{escaped}'"
418
+ else
419
+ # :nocov:
420
+ raise UnsupportedPgColumnTypeError.new(pg_type, entity_class)
421
+ # :nocov:
422
+ end
423
+ else
424
+ # :nocov:
425
+ raise UnsupportedPgColumnTypeError.new(pg_type, entity_class)
426
+ # :nocov:
427
+ end
428
+ end
429
+
430
+ def validate_intable!(value)
431
+ if value.is_a?(::String) || value.is_a?(::Symbol)
432
+ unless value =~ /\A[+-]?\d+\z/
433
+ # :nocov:
434
+ raise "Expected something that could be cast to an integer but got #{value}"
435
+ # :nocov:
436
+ end
437
+ end
438
+ end
439
+
440
+ def check_type_compatibility!(foobara_type, pg_info, pg_type: pg_info[:type], attribute_name: nil)
441
+ if foobara_type.extends?(:integer)
442
+ unless pg_type == "integer" || pg_type == "bigint" || pg_type == "_int4"
443
+ # :nocov:
444
+ raise ColumnTypeMismatchError.new(pg_type:, foobara_type:, attribute_name:, entity_class:)
445
+ # :nocov:
446
+ end
447
+ elsif foobara_type.extends?(:string) || foobara_type.extends?(:symbol)
448
+ unless pg_type == "text" || pg_type == "character varying"
449
+ # :nocov:
450
+ raise ColumnTypeMismatchError.new(pg_type:, foobara_type:, attribute_name:, entity_class:)
451
+ # :nocov:
452
+ end
453
+ elsif foobara_type.extends?(:datetime)
454
+ unless pg_type == "timestamp without time zone"
455
+ # :nocov:
456
+ raise ColumnTypeMismatchError.new(pg_type:, foobara_type:, attribute_name:, entity_class:)
457
+ # :nocov:
458
+ end
459
+ elsif foobara_type.extends?(:detached_entity)
460
+ check_type_compatibility!(foobara_type.target_class.primary_key_type, pg_info, pg_type:, attribute_name:)
461
+ elsif foobara_type.extends?(:model) || foobara_type.extends?(:attributes) ||
462
+ foobara_type == BuiltinTypes[:duck]
463
+ unless pg_type == "jsonb" || pg_type == "json"
464
+ # :nocov:
465
+ raise ColumnTypeMismatchError.new(pg_type:, foobara_type:, attribute_name:, entity_class:)
466
+ # :nocov:
467
+ end
468
+ elsif foobara_type.extends?(:array)
469
+ if pg_type == "ARRAY"
470
+ element_type = foobara_type.element_type
471
+ check_type_compatibility!(element_type, pg_info, pg_type: pg_info[:element_type], attribute_name:)
472
+ # :nocov:
473
+ elsif pg_type != json && pg_type != jsonb
474
+ raise ColumnTypeMismatchError.new(pg_type:, foobara_type:, attribute_name:, entity_class:)
475
+ # :nocov:
476
+ end
477
+ else
478
+ # :nocov:
479
+ raise UnsupportedPgColumnTypeError.new(pg_type, entity_class)
480
+ # :nocov:
481
+ end
482
+ end
409
483
  end
410
484
  end
411
485
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara-postgresql-crud-driver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi