Type strings, as in sqlr_column("email", "varchar(255)"), are read through a lookup table that mixes spellings from several engines (int4, bpchar, bytea, timestamptz, jsonb, double, …), and whatever the parser does not understand is dropped or passed through without an error:
library(sqlr)
as_sqlr_type("timestamp(3) with time zone")@with_timezone
#> [1] FALSE
as_sqlr_type("geometry(Point, 4326)")@name
#> [1] "geometry"
as_sqlr_type("varbinary(16)")@name
#> [1] "varbinary"
class(as_sqlr_type("varchr(255)"))[[1L]]
#> [1] "sqlr::sqlr_other_type"
The parser keeps the name before the first ( and the integers inside the parentheses, and ignores anything after the last ) (R/type_shorthand.R#L29-L48), which is how the time zone is lost. Names missing from the table fall through to sqlr_other(name) (#L90) with only the lower-cased name, although the documentation says unrecognised spellings are "carried through verbatim" (#L3-L5). A typo becomes an unmodelled type instead of an error.
The table is also the wrong thing to grow in core. Every dialect brings spellings to add, and some cannot be added without picking an engine: bare float is double precision in Postgres (docs) but a 4-byte float in MySQL (docs) and DuckDB (docs). Today it falls through, so a column written as "float" cannot compare equal to its own Postgres reflection, which sqlr.postgres reads as sqlr_double() (R/parse_type.R#L16):
library(sqlr)
sqlr_diff(
sqlr_table("t", sqlr_column("x", "float")),
sqlr_table("t", sqlr_column("x", sqlr_double()))
)
#> [1] "column x: type sqlr::sqlr_other_type(name=float) vs sqlr::sqlr_float_type(bytes=8)"
Proposal
Accept SQL-standard type names, plus text and uuid, as listed below, and reject everything else:
| Class |
Spellings |
sqlr_integer_type |
smallint, integer, int, bigint |
sqlr_float_type |
real, double precision, float(p) (4 bytes for p up to 24, else 8) |
sqlr_decimal_type |
numeric(p, s), decimal(p, s) |
sqlr_string_type |
char(n) and character(n) (n = 1 when omitted), varchar(n), character varying(n), text |
sqlr_binary_type |
binary(n) (fixed-length), varbinary(n), blob |
sqlr_boolean_type |
boolean |
sqlr_time_type |
date; time and timestamp, with optional (p) and an optional with time zone or without time zone |
sqlr_json_type |
json |
sqlr_uuid_type |
uuid |
Engine aliases such as int4, timestamptz and bytea are rejected, so the accepted set changes only when the IR does, not when a dialect is added. Unsigned and 1-byte integers and binary JSON have no standard spelling and stay constructor-only (sqlr_int(unsigned = TRUE), sqlr_integer_type(bytes = 1L), sqlr_json(binary = TRUE)). The error for an unknown spelling points to sqlr_other(), which becomes the only way to carry a type verbatim.
The same table, run in reverse, gives types a format() method. Then sqlr_diff() can report varchar(255) vs text rather than sqlr::sqlr_string_type(size=255, fixed=FALSE) vs sqlr::sqlr_string_type(size=NA, fixed=FALSE) (R/compare.R#L108-L119). Types without a standard spelling format as their constructor call.
The exported sqlr_parse_type() generic (R/generics.R#L24-L42) has no method in either dialect, because each reads types inside its sqlr_reflect_schema() method, so it can be dropped.
This is not the generic ANSI dialect the README rules out (README.md#L155-L159). Nothing renders this grammar; it only builds IR objects, and it is testable without a database.
SQLite reflection currently reads declared types through as_sqlr_type(), including non-standard spellings its renderer emits (DOUBLE, JSONB), so nbenn/sqlr.sqlite#1 has to land first.
Type strings, as in
sqlr_column("email", "varchar(255)"), are read through a lookup table that mixes spellings from several engines (int4,bpchar,bytea,timestamptz,jsonb,double, …), and whatever the parser does not understand is dropped or passed through without an error:The parser keeps the name before the first
(and the integers inside the parentheses, and ignores anything after the last)(R/type_shorthand.R#L29-L48), which is how the time zone is lost. Names missing from the table fall through tosqlr_other(name)(#L90) with only the lower-cased name, although the documentation says unrecognised spellings are "carried through verbatim" (#L3-L5). A typo becomes an unmodelled type instead of an error.The table is also the wrong thing to grow in core. Every dialect brings spellings to add, and some cannot be added without picking an engine: bare
floatisdouble precisionin Postgres (docs) but a 4-byte float in MySQL (docs) and DuckDB (docs). Today it falls through, so a column written as"float"cannot compare equal to its own Postgres reflection, which sqlr.postgres reads assqlr_double()(R/parse_type.R#L16):Proposal
Accept SQL-standard type names, plus
textanduuid, as listed below, and reject everything else:sqlr_integer_typesmallint,integer,int,bigintsqlr_float_typereal,double precision,float(p)(4 bytes forpup to 24, else 8)sqlr_decimal_typenumeric(p, s),decimal(p, s)sqlr_string_typechar(n)andcharacter(n)(n = 1when omitted),varchar(n),character varying(n),textsqlr_binary_typebinary(n)(fixed-length),varbinary(n),blobsqlr_boolean_typebooleansqlr_time_typedate;timeandtimestamp, with optional(p)and an optionalwith time zoneorwithout time zonesqlr_json_typejsonsqlr_uuid_typeuuidEngine aliases such as
int4,timestamptzandbyteaare rejected, so the accepted set changes only when the IR does, not when a dialect is added. Unsigned and 1-byte integers and binary JSON have no standard spelling and stay constructor-only (sqlr_int(unsigned = TRUE),sqlr_integer_type(bytes = 1L),sqlr_json(binary = TRUE)). The error for an unknown spelling points tosqlr_other(), which becomes the only way to carry a type verbatim.The same table, run in reverse, gives types a
format()method. Thensqlr_diff()can reportvarchar(255) vs textrather thansqlr::sqlr_string_type(size=255, fixed=FALSE) vs sqlr::sqlr_string_type(size=NA, fixed=FALSE)(R/compare.R#L108-L119). Types without a standard spelling format as their constructor call.The exported
sqlr_parse_type()generic (R/generics.R#L24-L42) has no method in either dialect, because each reads types inside itssqlr_reflect_schema()method, so it can be dropped.This is not the generic ANSI dialect the README rules out (
README.md#L155-L159). Nothing renders this grammar; it only builds IR objects, and it is testable without a database.SQLite reflection currently reads declared types through
as_sqlr_type(), including non-standard spellings its renderer emits (DOUBLE,JSONB), so nbenn/sqlr.sqlite#1 has to land first.