tina4ruby 3.13.14 → 3.13.17
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/lib/tina4/database.rb +12 -0
- data/lib/tina4/database_result.rb +15 -2
- data/lib/tina4/drivers/postgres_driver.rb +51 -0
- data/lib/tina4/orm.rb +71 -10
- data/lib/tina4/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 198d9905bf51e280f45f2bba90aac40fd64ea1bdf0c0f0b29e9a80d5e95020c9
|
|
4
|
+
data.tar.gz: 74c01ef1ac870c47aebf57e686a9b3bd7efa46f5c56c1d78df4c378102ee39f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03a840227106a3e2c12a78bde6c5d62ff8e4eb3041bc443fe73cad5e5d6d1dbd366ffa5458d34faeffe8448ad8fb19ce9bd74c821a4d3ec436ab80aa984b58fc
|
|
7
|
+
data.tar.gz: deefa98487ff855b8ad66beaa13b2e70f2b9f71677c0e317b52fc9153f43782a40723dc5519e814a2d65a29685fae18647f0bc84c124763aa8e5e7dcbddd07ca
|
data/lib/tina4/database.rb
CHANGED
|
@@ -405,6 +405,18 @@ module Tina4
|
|
|
405
405
|
nil
|
|
406
406
|
end
|
|
407
407
|
|
|
408
|
+
# Return the normalised engine name for this connection.
|
|
409
|
+
#
|
|
410
|
+
# Cross-framework parity with Python/PHP/Node ``get_database_type()``.
|
|
411
|
+
# ORM.create_table needs this to emit engine-correct DDL (SERIAL vs
|
|
412
|
+
# AUTOINCREMENT, BOOLEAN vs INTEGER, TIMESTAMP vs DATETIME). Returns the
|
|
413
|
+
# resolved driver key ("postgres", "mysql", "mssql", "firebird",
|
|
414
|
+
# "sqlite", ...) — the same alias-normalised value used to pick the
|
|
415
|
+
# driver class, so callers don't have to re-parse the connection string.
|
|
416
|
+
def get_database_type
|
|
417
|
+
@driver_name
|
|
418
|
+
end
|
|
419
|
+
|
|
408
420
|
# Execute a write statement. Returns true/false for simple writes.
|
|
409
421
|
# Returns DatabaseResult if SQL contains RETURNING, CALL, EXEC, or SELECT.
|
|
410
422
|
def execute(sql, params = [])
|
|
@@ -39,8 +39,21 @@ module Tina4
|
|
|
39
39
|
@records.empty?
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
# Index / slice access into the result rows.
|
|
43
|
+
#
|
|
44
|
+
# ``result[0]`` is documented (book ch5 §4 "Index Access"). Delegating
|
|
45
|
+
# straight to the materialised rows means every Array subscript form
|
|
46
|
+
# works — ``result[0]``, ``result[-1]``, ``result[1, 2]`` and
|
|
47
|
+
# ``result[1..3]`` — and matches Python's ``DatabaseResult.__getitem__``,
|
|
48
|
+
# which forwards to its records list.
|
|
49
|
+
def [](*args)
|
|
50
|
+
@records[*args]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Implicit array coercion, so a DatabaseResult can be splatted and used
|
|
54
|
+
# anywhere an Array is expected (``a, b = result``, ``[*result]``).
|
|
55
|
+
def to_ary
|
|
56
|
+
@records.dup
|
|
44
57
|
end
|
|
45
58
|
|
|
46
59
|
def length
|
|
@@ -25,6 +25,8 @@ module Tina4
|
|
|
25
25
|
url = uri.to_s
|
|
26
26
|
end
|
|
27
27
|
@connection = PG.connect(url)
|
|
28
|
+
apply_result_type_map(@connection)
|
|
29
|
+
@connection
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def close
|
|
@@ -151,6 +153,55 @@ module Tina4
|
|
|
151
153
|
|
|
152
154
|
private
|
|
153
155
|
|
|
156
|
+
# Decode result columns to native Ruby types (parity with SQLite, and
|
|
157
|
+
# with Python psycopg2 / Node node-postgres which both return native
|
|
158
|
+
# types). Without this the pg gem hands back EVERY column as a String —
|
|
159
|
+
# ``id: "1"``, ``active: "t"``, timestamps as strings — so an app
|
|
160
|
+
# written on SQLite silently changes behaviour on PostgreSQL.
|
|
161
|
+
#
|
|
162
|
+
# PG::BasicTypeMapForResults decodes by column OID:
|
|
163
|
+
# int2/int4/int8/serial -> Integer
|
|
164
|
+
# bool -> true / false
|
|
165
|
+
# float4/float8 -> Float
|
|
166
|
+
# numeric -> BigDecimal
|
|
167
|
+
# timestamp/timestamptz -> Time
|
|
168
|
+
# date -> Date
|
|
169
|
+
# text/varchar -> String (unchanged)
|
|
170
|
+
#
|
|
171
|
+
# The map is set on the *connection*, so it applies uniformly to both
|
|
172
|
+
# ``exec`` and ``exec_params`` and therefore to every fetch / fetch_one /
|
|
173
|
+
# columns path that flows through execute_query.
|
|
174
|
+
#
|
|
175
|
+
# uuid / json / jsonb / regclass have no built-in coder; left alone the
|
|
176
|
+
# map prints a noisy "no type cast defined" warning to stderr and falls
|
|
177
|
+
# back to a raw string anyway. We register explicit text decoders for them
|
|
178
|
+
# so they stay plain strings (the documented behaviour) without the
|
|
179
|
+
# warning — regclass matters because table_exists? selects to_regclass().
|
|
180
|
+
# bytea is already handled by the map as an ASCII-8BIT binary string,
|
|
181
|
+
# which is what decode_blobs expects.
|
|
182
|
+
def apply_result_type_map(conn)
|
|
183
|
+
type_map = PG::BasicTypeMapForResults.new(conn)
|
|
184
|
+
register_text_decoders(conn, type_map, %w[uuid json jsonb regclass])
|
|
185
|
+
conn.type_map_for_results = type_map
|
|
186
|
+
rescue PG::Error, NameError
|
|
187
|
+
# If the type map can't be built (e.g. a minimal pg build without
|
|
188
|
+
# BasicTypeMapForResults, or a connection that can't resolve OIDs),
|
|
189
|
+
# leave results as strings rather than breaking the connection.
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Register a plain-text decoder for each named PostgreSQL type so the
|
|
194
|
+
# result map returns it unchanged as a String instead of warning that no
|
|
195
|
+
# cast is defined. Unknown type names are skipped silently.
|
|
196
|
+
def register_text_decoders(conn, type_map, type_names)
|
|
197
|
+
type_names.each do |name|
|
|
198
|
+
oid = conn.exec_params("SELECT $1::regtype::oid", [name]).getvalue(0, 0).to_i
|
|
199
|
+
type_map.add_coder(PG::TextDecoder::String.new(oid: oid, name: name, format: 0))
|
|
200
|
+
rescue PG::Error
|
|
201
|
+
next
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
154
205
|
def convert_placeholders(sql)
|
|
155
206
|
counter = 0
|
|
156
207
|
sql.gsub("?") { counter += 1; "$#{counter}" }
|
data/lib/tina4/orm.rb
CHANGED
|
@@ -306,13 +306,29 @@ module Tina4
|
|
|
306
306
|
def create_table
|
|
307
307
|
return true if db.table_exists?(table_name)
|
|
308
308
|
|
|
309
|
-
# v3.13.
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
309
|
+
# v3.13.16: engine-aware DDL. Ruby used to emit SQLite-only DDL on
|
|
310
|
+
# every driver — INTEGER for booleans, DATETIME for datetimes, and a
|
|
311
|
+
# raw AUTOINCREMENT keyword — then ignore db.execute()'s return value
|
|
312
|
+
# and report success. On PostgreSQL the CREATE blew up
|
|
313
|
+
# ("syntax error at or near AUTOINCREMENT"), db.execute() swallowed it
|
|
314
|
+
# into get_error() and returned false, yet create_table still returned
|
|
315
|
+
# true with no table created — a silent, misleading pass.
|
|
316
|
+
#
|
|
317
|
+
# The fix mirrors the Python reference (tina4_python.orm.model):
|
|
318
|
+
# • get_database_type() now exists on Database (it didn't before, so
|
|
319
|
+
# the v3.13.11 BooleanField check never actually fired on Ruby).
|
|
320
|
+
# • BooleanField → native BOOLEAN (PG/MySQL) / BIT (MSSQL) /
|
|
321
|
+
# INTEGER (sqlite, firebird) — both PG aliases are matched.
|
|
322
|
+
# • DateTimeField → TIMESTAMP on PG/Firebird (neither has a DATETIME
|
|
323
|
+
# type), DATETIME elsewhere.
|
|
324
|
+
# • boolean DEFAULT is engine-aware: TRUE/FALSE for a native BOOLEAN,
|
|
325
|
+
# 1/0 for INTEGER/BIT-backed bools.
|
|
326
|
+
# • AUTOINCREMENT is translated per engine via SQLTranslator
|
|
327
|
+
# (SERIAL on PG, AUTO_INCREMENT on MySQL, IDENTITY on MSSQL, dropped
|
|
328
|
+
# on Firebird) instead of being emitted raw.
|
|
329
|
+
# • return false (not true) when the DDL fails.
|
|
315
330
|
engine = (db.respond_to?(:get_database_type) ? db.get_database_type : "").to_s.downcase
|
|
331
|
+
|
|
316
332
|
bool_sql = case engine
|
|
317
333
|
when "postgres", "postgresql" then "BOOLEAN"
|
|
318
334
|
when "mysql" then "BOOLEAN" # alias for TINYINT(1)
|
|
@@ -320,6 +336,15 @@ module Tina4
|
|
|
320
336
|
else "INTEGER" # sqlite, firebird, odbc, anything else
|
|
321
337
|
end
|
|
322
338
|
|
|
339
|
+
# PostgreSQL and Firebird have no DATETIME type — CREATE TABLE fails
|
|
340
|
+
# with `type "datetime" does not exist`. Emit each engine's real
|
|
341
|
+
# timestamp type. (MySQL/MSSQL/SQLite keep DATETIME: valid there, and
|
|
342
|
+
# on MySQL it avoids TIMESTAMP's auto-update + 2038 surprises.)
|
|
343
|
+
datetime_sql = case engine
|
|
344
|
+
when "postgres", "postgresql", "firebird" then "TIMESTAMP"
|
|
345
|
+
else "DATETIME"
|
|
346
|
+
end
|
|
347
|
+
|
|
323
348
|
type_map = {
|
|
324
349
|
integer: "INTEGER",
|
|
325
350
|
string: "VARCHAR(255)",
|
|
@@ -328,7 +353,7 @@ module Tina4
|
|
|
328
353
|
decimal: "REAL",
|
|
329
354
|
boolean: bool_sql,
|
|
330
355
|
date: "DATE",
|
|
331
|
-
datetime:
|
|
356
|
+
datetime: datetime_sql,
|
|
332
357
|
timestamp: "TIMESTAMP",
|
|
333
358
|
blob: "BLOB",
|
|
334
359
|
json: "TEXT"
|
|
@@ -346,15 +371,29 @@ module Tina4
|
|
|
346
371
|
parts << "AUTOINCREMENT" if opts[:auto_increment]
|
|
347
372
|
parts << "NOT NULL" if !opts[:nullable] && !opts[:primary_key]
|
|
348
373
|
if opts[:default] && !opts[:auto_increment]
|
|
349
|
-
|
|
350
|
-
parts << "DEFAULT #{default_val}"
|
|
374
|
+
parts << "DEFAULT #{default_literal(opts[:default], opts[:type], bool_sql)}"
|
|
351
375
|
end
|
|
352
376
|
col_defs << parts.join(" ")
|
|
353
377
|
end
|
|
354
378
|
|
|
355
379
|
sql = "CREATE TABLE IF NOT EXISTS #{table_name} (#{col_defs.join(', ')})"
|
|
356
|
-
|
|
380
|
+
|
|
381
|
+
# Translate AUTOINCREMENT to the engine's auto-increment syntax
|
|
382
|
+
# (INTEGER PRIMARY KEY AUTOINCREMENT -> SERIAL PRIMARY KEY on PG, etc.).
|
|
383
|
+
# SQLTranslator keys off the -ql spelling for postgres.
|
|
384
|
+
translator_engine = %w[postgres postgresql].include?(engine) ? "postgresql" : engine
|
|
385
|
+
sql = SQLTranslator.auto_increment_syntax(sql, translator_engine)
|
|
386
|
+
|
|
387
|
+
# Don't claim success when the DDL failed. db.execute() swallows the
|
|
388
|
+
# driver error into get_error() and returns false, so a bad type (or
|
|
389
|
+
# any DDL error) used to leave create_table returning true while no
|
|
390
|
+
# table was actually created.
|
|
391
|
+
ok = db.execute(sql)
|
|
357
392
|
db.commit
|
|
393
|
+
if ok == false
|
|
394
|
+
Tina4::Log.error("create_table failed for #{table_name}: #{db.get_error}", { sql: sql })
|
|
395
|
+
return false
|
|
396
|
+
end
|
|
358
397
|
true
|
|
359
398
|
end
|
|
360
399
|
|
|
@@ -427,6 +466,28 @@ module Tina4
|
|
|
427
466
|
results = db.fetch(sql, filter.values)
|
|
428
467
|
results.map { |row| from_hash(row) }
|
|
429
468
|
end
|
|
469
|
+
|
|
470
|
+
# Render a column DEFAULT literal for create_table.
|
|
471
|
+
#
|
|
472
|
+
# Strings are quoted. Booleans are engine-aware: a native BOOLEAN
|
|
473
|
+
# column (PG/MySQL) needs TRUE/FALSE, while INTEGER- and BIT-backed
|
|
474
|
+
# bools (SQLite, Firebird, MSSQL) need 1/0 — `DEFAULT 0` on a PG
|
|
475
|
+
# BOOLEAN raises "default expression is of type integer". Everything
|
|
476
|
+
# else is emitted as-is.
|
|
477
|
+
def default_literal(value, type, bool_sql)
|
|
478
|
+
if value.is_a?(String)
|
|
479
|
+
"'#{value}'"
|
|
480
|
+
elsif value == true || value == false || type == :boolean
|
|
481
|
+
truthy = value == true || value == 1 || value == "1"
|
|
482
|
+
if bool_sql == "BOOLEAN"
|
|
483
|
+
truthy ? "TRUE" : "FALSE"
|
|
484
|
+
else
|
|
485
|
+
truthy ? "1" : "0"
|
|
486
|
+
end
|
|
487
|
+
else
|
|
488
|
+
value.to_s
|
|
489
|
+
end
|
|
490
|
+
end
|
|
430
491
|
end
|
|
431
492
|
|
|
432
493
|
def initialize(attributes = {})
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|