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,
|
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 {
|
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