feat: ADR-0035 4a — SQL type-alias resolver (Type::from_sql_name)
Advanced-mode SQL type slot accepts the ten playground keywords plus the standard-SQL aliases (integer/varchar/timestamp/numeric/float/double precision/binary/..., case-insensitive). Simple-mode FromStr is unchanged (rejects aliases). Unknown names -> None for the friendly diagnostic.
This commit is contained in:
@@ -134,6 +134,41 @@ impl Type {
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a type name from the **advanced-mode SQL** type slot
|
||||
/// (ADR-0035 §3). Accepts the ten playground keywords *and* the
|
||||
/// standard-SQL aliases mapped onto them. Case-insensitive;
|
||||
/// internal whitespace is collapsed so `double precision` resolves
|
||||
/// regardless of spacing. Returns `None` for an unrecognised name —
|
||||
/// the caller turns that into the friendly "unknown type"
|
||||
/// diagnostic.
|
||||
///
|
||||
/// Deliberately distinct from [`FromStr`](std::str::FromStr), which
|
||||
/// is the *simple-mode* parser and accepts only the ten keywords
|
||||
/// (no aliases), so simple mode teaches the playground's own
|
||||
/// vocabulary. A length/precision argument (`varchar(255)`) is
|
||||
/// stripped by the grammar before the name reaches this resolver.
|
||||
#[must_use]
|
||||
pub fn from_sql_name(name: &str) -> Option<Self> {
|
||||
// Collapse internal whitespace (for the two-word
|
||||
// `double precision`) and lowercase for case-insensitive match.
|
||||
let normalised = name
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.to_ascii_lowercase();
|
||||
match normalised.as_str() {
|
||||
"integer" | "smallint" | "bigint" => Some(Self::Int),
|
||||
"varchar" | "char" => Some(Self::Text),
|
||||
"boolean" => Some(Self::Bool),
|
||||
"timestamp" => Some(Self::DateTime),
|
||||
"numeric" => Some(Self::Decimal),
|
||||
"float" | "double precision" => Some(Self::Real),
|
||||
"binary" | "varbinary" => Some(Self::Blob),
|
||||
// Fall through to the canonical ten keywords.
|
||||
other => other.parse::<Self>().ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
@@ -284,4 +319,64 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- ADR-0035 §3: advanced-mode SQL type-name resolution ---
|
||||
// `from_sql_name` accepts the ten playground keywords *and* the
|
||||
// standard-SQL aliases. Simple-mode `FromStr` is unchanged (it
|
||||
// still rejects aliases — see `unknown_type_lists_expected_alternatives`).
|
||||
|
||||
#[test]
|
||||
fn sql_resolver_accepts_the_ten_canonical_keywords() {
|
||||
for &ty in Type::all() {
|
||||
assert_eq!(Type::from_sql_name(ty.keyword()), Some(ty));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_resolver_maps_standard_sql_aliases() {
|
||||
for (alias, expected) in [
|
||||
("integer", Type::Int),
|
||||
("smallint", Type::Int),
|
||||
("bigint", Type::Int),
|
||||
("varchar", Type::Text),
|
||||
("char", Type::Text),
|
||||
("boolean", Type::Bool),
|
||||
("timestamp", Type::DateTime),
|
||||
("numeric", Type::Decimal),
|
||||
("float", Type::Real),
|
||||
("double precision", Type::Real),
|
||||
("binary", Type::Blob),
|
||||
("varbinary", Type::Blob),
|
||||
] {
|
||||
assert_eq!(
|
||||
Type::from_sql_name(alias),
|
||||
Some(expected),
|
||||
"alias `{alias}` should map to {expected:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_resolver_is_case_insensitive() {
|
||||
assert_eq!(Type::from_sql_name("INTEGER"), Some(Type::Int));
|
||||
assert_eq!(Type::from_sql_name("VarChar"), Some(Type::Text));
|
||||
assert_eq!(Type::from_sql_name("Double Precision"), Some(Type::Real));
|
||||
assert_eq!(Type::from_sql_name("TEXT"), Some(Type::Text));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_resolver_rejects_genuinely_unknown_names() {
|
||||
assert_eq!(Type::from_sql_name("money"), None);
|
||||
assert_eq!(Type::from_sql_name("json"), None);
|
||||
assert_eq!(Type::from_sql_name(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_resolver_does_not_accept_serial_aliases() {
|
||||
// `serial`/`shortid` are playground-only; there is no standard
|
||||
// alias for them, and INTEGER PRIMARY KEY does NOT become serial
|
||||
// (ADR-0035 §3 — that is enforced at the PK layer, not here).
|
||||
assert_eq!(Type::from_sql_name("serial"), Some(Type::Serial));
|
||||
assert_eq!(Type::from_sql_name("autoincrement"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user