Repository navigation
Declare column types at runtime (backend-neutral) and add a parameterized query API before v1.0 #28
Copy link
Copy link
Open
Labels
Description
Background
Relationizer needs column types at runtime, the moment it builds the SQL. Today the only runtime type source is the tuple values themselves (plus optional backend-specific symbols in the schema hash). That is the root of the library's recurring pain points:
- empty tuples and all-
nilcolumns carry zero type information (TypeNotFoundError/ReasonlessTypeError) - mixed
Integer/Floatcolumns fail inference - the schema vocabulary is backend-specific (
:INT64vs:INT8vs:BIGINT), so the same schema can't be reused across backends
Separately, values are hand-escaped into SQL strings. Every escaping rule is a place for injection-class bugs to hide; #21 and the BigQuery single-quote no-op (511412d) were both real ones. All three databases can take the same data as bound parameters instead:
- BigQuery:
UNNEST(@rows)with anARRAY<STRUCT<...>>query parameter - PostgreSQL:
jsonb_to_recordset($1)(orunnest($1::int8[], ...)) - MySQL:
JSON_TABLE(?, ...), since the backend already builds a JSON document
Static typing (RBS) was considered as the type source and rejected for that role: RBS is gone at runtime, and reading sig files at runtime would mean depending on the rbs gem. RBS still fits as a static check layered on top.
Direction
- Explicit runtime type declaration with a backend-neutral vocabulary (e.g.
:integer,:float,:decimal,:string,:boolean,:date,:timestamp,[:array, :integer]). Each backend maps it to its own SQL type. Value-based inference stays as the fallback when a type is omitted. - Accept
Dataclasses as row input (Data.define(:id, :name)) alongsideArray<Array>. Column order and names come from theDatamembers. - Parameterized API: a new method (working name
create_relation_query) returning[sql, params]for each backend.create_relation_literalstays as the convenience string API. - Ship
sig/relationizer.rbscovering the public API (both methods, error classes, the type vocabulary as a type alias).
Prerequisites
- None to start. Must be decided and merged before Cut v1.0.0 (API freeze) #25 (v1.0.0 API freeze), because adding it afterwards changes the frozen surface.
- Can land after Cut v0.5.0 #24 (v0.5.0) or be part of it. That is a human call.
Open questions (human decision)
- Final names of the neutral type symbols, and whether backend-specific symbols (
:INT64etc.) stay accepted as an escape hatch - Method name for the parameterized API, and the shape of
paramsper backend (positional array for PG/MySQL vs. a named hash for BigQuery) - Whether to add callable module functions (
Relationizer::MySQL.create_relation_query(...)) in addition toinclude - Split into sub-issues per step above, or ship as one
Acceptance criteria
- Neutral type vocabulary implemented in all three backends, with a mapping table in README
- Empty tuples and all-
nilcolumns work without errors when types are declared neutrally -
Datainstances accepted as rows; mismatched members vs. schema raise a gem-level error -
create_relation_queryimplemented for all three backends; the generated SQL contains no user values (only placeholders) -
sig/relationizer.rbsadded andsteep check(orrbs validate) passes in CI -
test/consistency_test.rbextended to cover the neutral types and the parameterized API across backends - All new behavior done TDD;
bundle exec rakegreen on the CI matrix
Verification
- Unit tests per backend plus the consistency matrix
- Live execution of the parameterized SQL with bound params against local PostgreSQL (
psql/pggem) and MySQL (mysql2ortrilogy), as done ad hoc for MySQL & BigQuery backends don't escape backticks in identifiers (MySQL JSON PATH also unquoted) #21. BigQuery can only be checked against the documented parameter format unless billing access is available.
Reactions are currently unavailable
Activity