///|
/// Type Mapping Layer — P0-007.
/// PostgreSQL ↔ MoonBit type mapping table.
/// All mapping decisions inform ADR-004 (Naming Convention) and ADR-002 (Runtime Scope).
//
// Copyright 2026 Mairzzcllo
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///|
/// Map a PG type name to the corresponding Value constructor name string.
/// Returns the string name of the Value variant (e.g., "Int64", "Double", "String", etc.).
/// This is the SINGLE source of truth for PG type → MoonBit Value constructor mapping.
/// Normalizes input: case-insensitive (via to_lower), strips schema prefix.
pub fn map_pg_to_value_constructor_str(name : String) -> String {
// Normalize: lowercase for case-insensitive matching, strip schema prefix
let normalized = {
let lower = name.to_lower()
let dot_idx = lower.rev_find(".")
match dot_idx {
Some(pos) => lower[:][pos + 1:]
None => lower
}
}
match normalized {
// Small/medium ints → Int64 (64-bit required, matches Value::Int64)
"int2"
| "smallint"
| "smallserial"
| "int4"
| "integer"
| "int"
| "serial" => "Int64"
// Large ints → Int64 (64-bit required)
"int8" | "bigint" | "bigserial" => "Int64"
// Floats — float4/real map to Double (runtime has get_double, not get_float)
"float4" | "real" | "float8" | "double" | "double precision" => "Double"
// numeric/decimal → Decimal (preserve full precision).
"numeric" | "decimal" => "Decimal"
// Boolean
"bool" | "boolean" => "Bool"
// String types
"text" | "varchar" | "char" | "bpchar" | "name" | "character varying" =>
"String"
// Binary
"bytea" => "Bytes"
// JSON
"json" | "jsonb" => "JsonValue"
// Network
"inet" | "cidr" | "macaddr" => "IpAddr"
// UUID
"uuid" => "Uuid"
// Date/Time — date→Date, timestamp→DateTime, time→Time
"date" => "Date"
"timestamp" | "timestamptz" | "timestamp with time zone" => "DateTime"
"time" => "Time"
"timetz" => "TimeTZ"
// Interval → Duration (microseconds).
"interval" => "Duration"
// Arrays are handled by the caller via Arr(inner)
// Fallback for unknown types
_ => {
debug_inspect("unknown PG type, falling back to String: " + name) catch {
_ => ()
}
"String"
}
}
}
///|
/// Look up an override for a PG type name in the overrides list.
/// Strips schema prefix (e.g., "pg_catalog.int4" → "int4") before lookup.
/// Returns the mapped MoonBit type name if found, None otherwise.
fn lookup_override(
name : String,
overrides : Array[(String, String)],
) -> String? {
let lower = name.to_lower()
let base = match lower.rev_find(".") {
Some(pos) => lower[:][pos + 1:].to_owned()
None => lower
}
for o in overrides {
if o.0 == base {
return Some(o.1)
}
}
None
}
///|
/// PostgreSQL canonical/common aliases → MoonBit type mapping.
/// Returns the MoonBit TypeExpr for the given PG type name.
/// Delegates to map_pg_to_value_constructor_str for the canonical mapping.
fn map_pg_name(
name : String,
overrides : Array[(String, String)],
enum_renames : Array[(String, String)],
) -> TypeExpr {
match lookup_enum_rename(name, enum_renames) {
Some(renamed) => Named(renamed)
None =>
match lookup_override(name, overrides) {
Some(mapped) => Named(mapped)
None => Named(map_pg_to_value_constructor_str(name))
}
}
}
///|
/// Look up a disambiguated MoonBit type name for a PG enum type.
fn lookup_enum_rename(
name : String,
enum_renames : Array[(String, String)],
) -> String? {
let lower = name.to_lower()
let base = match lower.rev_find(".") {
Some(pos) => lower[:][pos + 1:].to_owned()
None => lower
}
for r in enum_renames {
if r.0 == base {
return Some(r.1)
}
}
None
}
///|
/// Map an IR InternalType to a MoonBit TypeExpr.
/// Handles Named types via the mapping table and Arr types recursively.
pub fn map_internal_type(
ir_type : InternalType,
overrides : Array[(String, String)],
enum_renames : Array[(String, String)],
) -> TypeExpr {
match ir_type {
Named(name) => map_pg_name(name, overrides, enum_renames)
Arr(elem) =>
Parameterized("Array", [map_internal_type(elem, overrides, enum_renames)])
}
}
///|
/// Look up a column-level override for a specific table.column.
/// Returns the mapped MoonBit type name if found, None otherwise.
fn lookup_column_override(
table : String,
column : String,
column_overrides : Array[ColumnOverride],
) -> String? {
for o in column_overrides {
if o.table == table && o.column == column {
return Some(o.moonbit_type)
}
}
None
}
///|
/// Check if a PG type name has a nullable override that forces it to be nullable
/// even when the schema says not_null=true.
fn is_nullable_override(
name : String,
nullable_overrides : Array[String],
) -> Bool {
let lower = name.to_lower()
let base = match lower.rev_find(".") {
Some(pos) => lower[:][pos + 1:].to_owned()
None => lower
}
for n in nullable_overrides {
if n == base {
return true
}
}
false
}
///|
/// Map an IR InternalType to a MoonBit TypeExpr, considering nullability.
/// When `not_null` is false, wraps in `Option[...]`.
/// When `not_null` is true but the type is in `nullable_overrides`, also wraps in `Option[...]`.
pub fn map_internal_type_nullable(
ir_type : InternalType,
not_null : Bool,
overrides : Array[(String, String)],
nullable_overrides : Array[String],
enum_renames : Array[(String, String)],
) -> TypeExpr {
let base = map_internal_type(ir_type, overrides, enum_renames)
if not_null {
// Check if nullable override forces this type to be nullable
let force_nullable = match ir_type {
Named(name) => is_nullable_override(name, nullable_overrides)
Arr(_) => false
}
if force_nullable {
Option(base)
} else {
base
}
} else {
Option(base)
}
}
///|
/// Map an IR InternalType to a Value constructor name, respecting overrides.
/// If an override is found, returns the override target as the constructor name.
pub fn type_to_value_constructor(
ir_type : InternalType,
overrides : Array[(String, String)],
) -> String {
match ir_type {
Named(name) =>
match lookup_override(name, overrides) {
Some(mapped) => mapped
None => map_pg_to_value_constructor_str(name)
}
Arr(_) => "String"
}
}
///|
fn normalize_pg_type_name(name : String) -> String {
let lower = name.to_lower()
match lower.rev_find(".") {
Some(pos) => lower[:][pos + 1:].to_owned()
None => lower
}
}
///|
/// Returns true when the PG type has an explicit mapping (not String fallback).
fn is_known_pg_type(name : String) -> Bool {
match normalize_pg_type_name(name) {
"int2"
| "smallint"
| "smallserial"
| "int4"
| "integer"
| "int"
| "serial" => true
"int8" | "bigint" | "bigserial" => true
"float4" | "real" | "float8" | "double" | "double precision" => true
"numeric" | "decimal" => true
"bool" | "boolean" => true
"text" | "varchar" | "char" | "bpchar" | "name" | "character varying" =>
true
"bytea" => true
"json" | "jsonb" => true
"inet" | "cidr" | "macaddr" => true
"uuid" => true
"date" => true
"timestamp" | "timestamptz" | "timestamp with time zone" => true
"time" => true
"timetz" => true
"interval" => true
_ => false
}
}
///|
fn check_pg_type_known(
name : String,
overrides : Array[(String, String)],
) -> Unit raise {
match lookup_override(name, overrides) {
Some(_) => ()
None =>
if is_known_pg_type(name) {
()
} else {
raise Failure::Failure("unknown PG type: " + name)
}
}
}
///|
/// Fail codegen on unknown PG types (override-aware). Always enforced.
pub fn validate_strict_types(
catalog : AdaptCatalog,
queries : Array[AdaptQuery],
overrides : Array[(String, String)],
_strict_types : Bool,
) -> Unit raise {
for schema in catalog.schemas {
for table in schema.tables {
for col in table.columns {
check_pg_type_known(col.ty.name, overrides)
}
}
}
for q in queries {
for col in q.columns {
check_pg_type_known(col.ty.name, overrides)
}
for p in q.params {
check_pg_type_known(p.column.ty.name, overrides)
}
}
}
// === Tests ===
///|
test "map_pg_name: int4 maps to Int64" {
let t = map_pg_name("int4", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: int8 maps to Int64" {
let t = map_pg_name("int8", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: int2 maps to Int64" {
let t = map_pg_name("int2", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: float4 maps to Double" {
let t = map_pg_name("float4", [], [])
debug_inspect(t, content="Named(\"Double\")")
}
///|
test "map_pg_name: float8 maps to Double" {
let t = map_pg_name("float8", [], [])
debug_inspect(t, content="Named(\"Double\")")
}
///|
test "map_pg_name: bool maps to Bool" {
let t = map_pg_name("bool", [], [])
debug_inspect(t, content="Named(\"Bool\")")
}
///|
test "map_pg_name: text maps to String" {
let t = map_pg_name("text", [], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: varchar maps to String" {
let t = map_pg_name("varchar", [], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: timestamptz maps to DateTime" {
let t = map_pg_name("timestamptz", [], [])
debug_inspect(t, content="Named(\"DateTime\")")
}
///|
test "map_pg_name: jsonb maps to JsonValue" {
let t = map_pg_name("jsonb", [], [])
debug_inspect(t, content="Named(\"JsonValue\")")
}
///|
test "map_pg_name: bytea maps to Bytes" {
let t = map_pg_name("bytea", [], [])
debug_inspect(t, content="Named(\"Bytes\")")
}
///|
test "map_pg_name: uuid maps to Uuid" {
let t = map_pg_name("uuid", [], [])
debug_inspect(t, content="Named(\"Uuid\")")
}
///|
test "map_pg_name: inet maps to IpAddr" {
let t = map_pg_name("inet", [], [])
debug_inspect(t, content="Named(\"IpAddr\")")
}
///|
test "map_pg_name: date maps to Date" {
let t = map_pg_name("date", [], [])
debug_inspect(t, content="Named(\"Date\")")
}
///|
test "map_pg_name: numeric maps to Decimal" {
let t = map_pg_name("numeric", [], [])
debug_inspect(t, content="Named(\"Decimal\")")
}
///|
test "map_pg_name: unknown type falls back to String" {
let t = map_pg_name("some_unknown_type", [], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "validate_strict_types: rejects unknown PG type when enabled" {
let cat : AdaptCatalog = {
name: "test",
default_schema: "public",
schemas: [
{
name: "public",
comment: "",
tables: [
{
name: "users",
schema: "public",
comment: "",
columns: [
{
name: "weird",
not_null: true,
is_array: false,
ty: { catalog: "", schema: "", name: "some_unknown_type" },
length: 0,
comment: "",
table: { catalog: "", schema: "", name: "users" },
},
],
},
],
enums: [],
},
],
}
let result = try {
validate_strict_types(cat, [], [], true)
"unexpected_ok"
} catch {
_ => "rejected"
}
inspect(result, content="rejected")
}
///|
test "map_pg_name: boolean alias" {
let t = map_pg_name("boolean", [], [])
debug_inspect(t, content="Named(\"Bool\")")
}
///|
test "map_pg_name: smallint alias" {
let t = map_pg_name("smallint", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: bigint alias" {
let t = map_pg_name("bigint", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_internal_type: Named int4 maps to Int64" {
let t = map_internal_type(Named("int4"), [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_internal_type: Arr of int4 maps to Array[Int64]" {
let t = map_internal_type(Arr(Named("int4")), [], [])
debug_inspect(t, content="Parameterized(\"Array\", [Named(\"Int64\")])")
}
///|
test "map_internal_type: nested Arr maps to Array[Array[Int64]]" {
let t = map_internal_type(Arr(Arr(Named("int4"))), [], [])
debug_inspect(
t,
content="Parameterized(\"Array\", [Parameterized(\"Array\", [Named(\"Int64\")])])",
)
}
///|
test "map_internal_type_nullable: not_null preserves type" {
let t = map_internal_type_nullable(Named("int4"), true, [], [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_internal_type_nullable: nullable wraps in Option" {
let t = map_internal_type_nullable(Named("int4"), false, [], [], [])
debug_inspect(t, content="Option(Named(\"Int64\"))")
}
///|
test "map_internal_type_nullable: nullable array wraps in Option" {
let t = map_internal_type_nullable(Arr(Named("text")), false, [], [], [])
debug_inspect(
t,
content="Option(Parameterized(\"Array\", [Named(\"String\")]))",
)
}
///|
test "map_pg_name: real alias" {
let t = map_pg_name("real", [], [])
debug_inspect(t, content="Named(\"Double\")")
}
///|
test "map_pg_name: decimal alias" {
let t = map_pg_name("decimal", [], [])
debug_inspect(t, content="Named(\"Decimal\")")
}
///|
test "map_pg_name: serial maps to Int64" {
let t = map_pg_name("serial", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: bigserial maps to Int64" {
let t = map_pg_name("bigserial", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: smallserial maps to Int64" {
let t = map_pg_name("smallserial", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: INT4 uppercase maps to Int64" {
let t = map_pg_name("INT4", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: BIGINT uppercase maps to Int64" {
let t = map_pg_name("BIGINT", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: TEXT uppercase maps to String" {
let t = map_pg_name("TEXT", [], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: pg_catalog.int4 strips schema prefix" {
let t = map_pg_name("pg_catalog.int4", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: pg_catalog.int8 strips schema prefix" {
let t = map_pg_name("pg_catalog.int8", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: pg_catalog.text strips schema prefix" {
let t = map_pg_name("pg_catalog.text", [], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: pg_catalog.INT4 uppercase schema prefix strips correctly" {
let t = map_pg_name("pg_catalog.INT4", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: pg_catalog.BIGSERIAL strips schema prefix" {
let t = map_pg_name("pg_catalog.BIGSERIAL", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: timestamp with time zone maps to DateTime" {
let t = map_pg_name("timestamp with time zone", [], [])
debug_inspect(t, content="Named(\"DateTime\")")
}
///|
test "map_pg_name: double precision maps to Double" {
let t = map_pg_name("double precision", [], [])
debug_inspect(t, content="Named(\"Double\")")
}
///|
test "map_pg_name: DOUBLE PRECISION uppercase maps to Double" {
let t = map_pg_name("DOUBLE PRECISION", [], [])
debug_inspect(t, content="Named(\"Double\")")
}
///|
test "map_pg_name: pg_catalog.float8 strips schema prefix" {
let t = map_pg_name("pg_catalog.float8", [], [])
debug_inspect(t, content="Named(\"Double\")")
}
///|
test "map_pg_name: serial type pg_catalog.SERIAL strips prefix" {
let t = map_pg_name("pg_catalog.SERIAL", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
// === map_pg_to_value_constructor_str tests ===
///|
test "map_pg_to_value_constructor_str: int4 maps to Int64" {
let s = map_pg_to_value_constructor_str("int4")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: int8 maps to Int64" {
let s = map_pg_to_value_constructor_str("int8")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: text maps to String" {
let s = map_pg_to_value_constructor_str("text")
debug_inspect(s, content="\"String\"")
}
///|
test "map_pg_to_value_constructor_str: bool maps to Bool" {
let s = map_pg_to_value_constructor_str("bool")
debug_inspect(s, content="\"Bool\"")
}
///|
test "map_pg_to_value_constructor_str: float8 maps to Double" {
let s = map_pg_to_value_constructor_str("float8")
debug_inspect(s, content="\"Double\"")
}
///|
test "map_pg_to_value_constructor_str: jsonb maps to JsonValue" {
let s = map_pg_to_value_constructor_str("jsonb")
debug_inspect(s, content="\"JsonValue\"")
}
///|
test "map_pg_to_value_constructor_str: bytea maps to Bytes" {
let s = map_pg_to_value_constructor_str("bytea")
debug_inspect(s, content="\"Bytes\"")
}
///|
test "map_pg_to_value_constructor_str: uuid maps to Uuid" {
let s = map_pg_to_value_constructor_str("uuid")
debug_inspect(s, content="\"Uuid\"")
}
///|
test "map_pg_to_value_constructor_str: inet maps to IpAddr" {
let s = map_pg_to_value_constructor_str("inet")
debug_inspect(s, content="\"IpAddr\"")
}
///|
test "map_pg_to_value_constructor_str: date maps to Date" {
let s = map_pg_to_value_constructor_str("date")
debug_inspect(s, content="\"Date\"")
}
///|
test "map_pg_to_value_constructor_str: numeric maps to Decimal" {
let s = map_pg_to_value_constructor_str("numeric")
debug_inspect(s, content="\"Decimal\"")
}
///|
test "map_pg_to_value_constructor_str: unknown falls back to String" {
let s = map_pg_to_value_constructor_str("some_unknown")
debug_inspect(s, content="\"String\"")
}
///|
test "map_pg_to_value_constructor_str: INT4 uppercase maps to Int64" {
let s = map_pg_to_value_constructor_str("INT4")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: pg_catalog.int4 strips schema prefix" {
let s = map_pg_to_value_constructor_str("pg_catalog.int4")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: timestamp with time zone maps to DateTime" {
let s = map_pg_to_value_constructor_str("timestamp with time zone")
debug_inspect(s, content="\"DateTime\"")
}
///|
test "map_pg_to_value_constructor_str: double precision maps to Double" {
let s = map_pg_to_value_constructor_str("double precision")
debug_inspect(s, content="\"Double\"")
}
///|
test "map_pg_to_value_constructor_str: time maps to Time" {
let s = map_pg_to_value_constructor_str("time")
debug_inspect(s, content="\"Time\"")
}
///|
test "map_pg_to_value_constructor_str: timetz maps to TimeTZ" {
let s = map_pg_to_value_constructor_str("timetz")
debug_inspect(s, content="\"TimeTZ\"")
}
///|
test "map_pg_to_value_constructor_str: TIMETZ uppercase maps to TimeTZ" {
let s = map_pg_to_value_constructor_str("TIMETZ")
debug_inspect(s, content="\"TimeTZ\"")
}
///|
test "map_pg_to_value_constructor_str: pg_catalog.timetz strips schema prefix maps to TimeTZ" {
let s = map_pg_to_value_constructor_str("pg_catalog.timetz")
debug_inspect(s, content="\"TimeTZ\"")
}
///|
test "map_pg_name: timetz maps to TimeTZ" {
let t = map_pg_name("timetz", [], [])
debug_inspect(t, content="Named(\"TimeTZ\")")
}
///|
test "map_internal_type: Named timetz maps to TimeTZ" {
let t = map_internal_type(Named("timetz"), [], [])
debug_inspect(t, content="Named(\"TimeTZ\")")
}
///|
test "map_pg_to_value_constructor_str: interval maps to Duration" {
let s = map_pg_to_value_constructor_str("interval")
debug_inspect(s, content="\"Duration\"")
}
///|
test "map_pg_to_value_constructor_str: macaddr maps to IpAddr" {
let s = map_pg_to_value_constructor_str("macaddr")
debug_inspect(s, content="\"IpAddr\"")
}
///|
test "map_pg_to_value_constructor_str: char maps to String" {
let s = map_pg_to_value_constructor_str("char")
debug_inspect(s, content="\"String\"")
}
///|
test "map_pg_to_value_constructor_str: bpchar maps to String" {
let s = map_pg_to_value_constructor_str("bpchar")
debug_inspect(s, content="\"String\"")
}
///|
test "map_pg_to_value_constructor_str: smallint maps to Int64" {
let s = map_pg_to_value_constructor_str("smallint")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: bigint maps to Int64" {
let s = map_pg_to_value_constructor_str("bigint")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: real maps to Double" {
let s = map_pg_to_value_constructor_str("real")
debug_inspect(s, content="\"Double\"")
}
///|
test "map_pg_to_value_constructor_str: varchar maps to String" {
let s = map_pg_to_value_constructor_str("varchar")
debug_inspect(s, content="\"String\"")
}
///|
test "map_pg_to_value_constructor_str: timestamptz maps to DateTime" {
let s = map_pg_to_value_constructor_str("timestamptz")
debug_inspect(s, content="\"DateTime\"")
}
///|
test "map_pg_to_value_constructor_str: cidr maps to IpAddr" {
let s = map_pg_to_value_constructor_str("cidr")
debug_inspect(s, content="\"IpAddr\"")
}
///|
test "map_pg_to_value_constructor_str: serial maps to Int64" {
let s = map_pg_to_value_constructor_str("serial")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: bigserial maps to Int64" {
let s = map_pg_to_value_constructor_str("bigserial")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: smallserial maps to Int64" {
let s = map_pg_to_value_constructor_str("smallserial")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: pg_catalog.int8 strips schema prefix" {
let s = map_pg_to_value_constructor_str("pg_catalog.int8")
debug_inspect(s, content="\"Int64\"")
}
///|
test "map_pg_to_value_constructor_str: pg_catalog.text strips schema prefix" {
let s = map_pg_to_value_constructor_str("pg_catalog.text")
debug_inspect(s, content="\"String\"")
}
// === Override tests ===
///|
test "map_pg_name: override numeric to Decimal" {
let t = map_pg_name("numeric", [("numeric", "Decimal")], [])
debug_inspect(t, content="Named(\"Decimal\")")
}
///|
test "map_pg_name: override int4 to String" {
let t = map_pg_name("int4", [("int4", "String")], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: override INT4 uppercase to String" {
let t = map_pg_name("INT4", [("int4", "String")], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: override unknown type to custom type" {
let t = map_pg_name("my_custom_type", [("my_custom_type", "MyCustomType")], [])
debug_inspect(t, content="Named(\"MyCustomType\")")
}
///|
test "map_pg_name: override with multiple entries picks first match" {
let overrides : Array[(String, String)] = [
("int4", "String"),
("int4", "Int64"),
]
let t = map_pg_name("int4", overrides, [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_pg_name: empty overrides falls back to default" {
let t = map_pg_name("int4", [], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: overrides with non-matching entry falls back to default" {
let t = map_pg_name("int4", [("text", "MyString")], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_pg_name: pg_catalog.int4 with override works with schema prefix" {
let t = map_pg_name("pg_catalog.int4", [("int4", "String")], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_internal_type: override Named type" {
let t = map_internal_type(Named("int4"), [("int4", "String")], [])
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_internal_type: override in array element" {
let t = map_internal_type(Arr(Named("int4")), [("int4", "String")], [])
debug_inspect(t, content="Parameterized(\"Array\", [Named(\"String\")])")
}
///|
test "map_internal_type_nullable: override with not_null" {
let t = map_internal_type_nullable(
Named("int4"),
true,
[("int4", "String")],
[],
[],
)
debug_inspect(t, content="Named(\"String\")")
}
///|
test "map_internal_type_nullable: override with nullable wraps in Option" {
let t = map_internal_type_nullable(
Named("int4"),
false,
[("int4", "String")],
[],
[],
)
debug_inspect(t, content="Option(Named(\"String\"))")
}
///|
test "type_to_value_constructor: override affects value constructor" {
let t = type_to_value_constructor(Named("int4"), [("int4", "String")])
debug_inspect(t, content="\"String\"")
}
///|
test "type_to_value_constructor: empty overrides uses default" {
let t = type_to_value_constructor(Named("int4"), [])
debug_inspect(t, content="\"Int64\"")
}
///|
test "type_to_value_constructor: array returns String regardless of override" {
let t = type_to_value_constructor(Arr(Named("int4")), [("int4", "String")])
debug_inspect(t, content="\"String\"")
}
///|
test "lookup_override: finds matching entry" {
let overrides : Array[(String, String)] = [
("numeric", "Decimal"),
("int4", "String"),
]
let r = lookup_override("numeric", overrides)
debug_inspect(r, content="Some(\"Decimal\")")
}
///|
test "lookup_override: case-insensitive match" {
let overrides : Array[(String, String)] = [("int4", "String")]
let r = lookup_override("INT4", overrides)
debug_inspect(r, content="Some(\"String\")")
}
///|
test "lookup_override: not found returns None" {
let r = lookup_override("unknown", [("int4", "String")])
debug_inspect(r, content="None")
}
///|
test "lookup_override: empty overrides returns None" {
let r = lookup_override("int4", [])
debug_inspect(r, content="None")
}
// === lookup_column_override tests ===
///|
test "lookup_column_override: matching table.column returns type" {
let overrides : Array[ColumnOverride] = [
{ table: "users", column: "id", moonbit_type: "String" },
]
let r = lookup_column_override("users", "id", overrides)
debug_inspect(r, content="Some(\"String\")")
}
///|
test "lookup_column_override: non-matching table returns None" {
let overrides : Array[ColumnOverride] = [
{ table: "users", column: "id", moonbit_type: "String" },
]
let r = lookup_column_override("posts", "id", overrides)
debug_inspect(r, content="None")
}
///|
test "lookup_column_override: non-matching column returns None" {
let overrides : Array[ColumnOverride] = [
{ table: "users", column: "id", moonbit_type: "String" },
]
let r = lookup_column_override("users", "name", overrides)
debug_inspect(r, content="None")
}
///|
test "lookup_column_override: empty list returns None" {
let r = lookup_column_override("users", "id", [])
debug_inspect(r, content="None")
}
///|
test "lookup_column_override: multiple overrides picks correct match" {
let overrides : Array[ColumnOverride] = [
{ table: "users", column: "id", moonbit_type: "String" },
{ table: "posts", column: "title", moonbit_type: "Int64" },
{ table: "users", column: "name", moonbit_type: "Bool" },
]
let r1 = lookup_column_override("users", "name", overrides)
debug_inspect(r1, content="Some(\"Bool\")")
let r2 = lookup_column_override("posts", "title", overrides)
debug_inspect(r2, content="Some(\"Int64\")")
}
// === is_nullable_override tests ===
///|
test "is_nullable_override: matching pg_type returns true" {
let r = is_nullable_override("int4", ["int4"])
debug_inspect(r, content="true")
}
///|
test "is_nullable_override: non-matching pg_type returns false" {
let r = is_nullable_override("text", ["int4"])
debug_inspect(r, content="false")
}
///|
test "is_nullable_override: empty list returns false" {
let r = is_nullable_override("int4", [])
debug_inspect(r, content="false")
}
///|
test "is_nullable_override: matching with schema prefix returns true" {
let r = is_nullable_override("pg_catalog.int4", ["int4"])
debug_inspect(r, content="true")
}
///|
test "is_nullable_override: uppercase input normalized" {
let r = is_nullable_override("INT4", ["int4"])
debug_inspect(r, content="true")
}
///|
test "is_nullable_override: multiple nullable_overrides finds match" {
let r = is_nullable_override("text", ["int4", "text", "bool"])
debug_inspect(r, content="true")
}
// === map_internal_type_nullable with nullable_overrides ===
///|
test "map_internal_type_nullable: not_null with matching nullable_override wraps in Option" {
let t = map_internal_type_nullable(Named("int4"), true, [], ["int4"], [])
debug_inspect(t, content="Option(Named(\"Int64\"))")
}
///|
test "map_internal_type_nullable: not_null with non-matching nullable_override keeps type" {
let t = map_internal_type_nullable(Named("int4"), true, [], ["text"], [])
debug_inspect(t, content="Named(\"Int64\")")
}
///|
test "map_internal_type_nullable: nullable_override does not affect array types" {
let t = map_internal_type_nullable(Arr(Named("int4")), true, [], ["int4"], [])
debug_inspect(t, content="Parameterized(\"Array\", [Named(\"Int64\")])")
}
///|
test "map_internal_type_nullable: override with matching nullable_override" {
let t = map_internal_type_nullable(
Named("int4"),
true,
[("int4", "String")],
["int4"],
[],
)
debug_inspect(t, content="Option(Named(\"String\"))")
}
///|
test "map_internal_type_nullable: nullable with matching nullable_override wraps in Option" {
let t = map_internal_type_nullable(Named("int4"), false, [], ["int4"], [])
debug_inspect(t, content="Option(Named(\"Int64\"))")
}