// Values on the wire, and the traits that move MoonBit types across it.
//
// `SqlEncode` and `SqlDecode` are the two directions. `SqlOrd` and `SqlNum`
// are markers that carry no methods: they exist only to keep an operator from
// being offered on a type SQL cannot apply it to.
///|
/// A value as it travels to or from the database.
pub(all) enum SqlValue {
VNull
VInt(Int64)
VDouble(Double)
VText(String)
VBool(Bool)
VBytes(Bytes)
} derive(Debug, Eq)
///|
/// SQL dialect. The two differ in how a placeholder is spelled.
pub(all) enum Dialect {
Sqlite
Postgres
} derive(Debug, Eq)
///|
/// A stored value did not fit the type the entity declared for it.
pub(all) suberror DecodeError {
MissingColumn(String)
TypeMismatch(column~ : String, expected~ : String)
Malformed(String)
} derive(Debug)
///|
/// How a MoonBit value is written to a column.
pub(open) trait SqlEncode {
fn to_sql_value(Self) -> SqlValue
}
///|
/// How a column is read back into a MoonBit value.
///
/// The second argument is the column's name, carried only so that a failure
/// can say which column it came from.
pub(open) trait SqlDecode {
fn decode(SqlValue, String) -> Self raise DecodeError
}
///|
/// Marker trait for types SQL will order: `gt`, `gte`, `lt`, `lte`, `asc`,
/// `desc`. No methods — it is a constraint, not an interface.
pub(open) trait SqlOrd {}
///|
/// Marker trait for types SQL will do arithmetic on.
///
/// Narrower than `SqlOrd` on purpose: `sum` and `avg` need a number, not
/// merely something orderable, so a `Column[String]` cannot be summed.
pub(open) trait SqlNum {}
///|
pub impl SqlEncode for Int with fn to_sql_value(self) {
VInt(self.to_int64())
}
///|
pub impl SqlEncode for Int64 with fn to_sql_value(self) {
VInt(self)
}
///|
pub impl SqlEncode for String with fn to_sql_value(self) {
VText(self)
}
///|
pub impl SqlEncode for Bool with fn to_sql_value(self) {
VBool(self)
}
///|
pub impl SqlEncode for Double with fn to_sql_value(self) {
VDouble(self)
}
///|
/// A nullable column writes `NULL` for `None`.
///
/// The mirror of the `SqlDecode for T?` instance: without it, an entity with
/// any optional field cannot be inserted.
pub impl[T : SqlEncode] SqlEncode for T? with fn to_sql_value(self) {
match self {
None => VNull
Some(x) => SqlEncode::to_sql_value(x)
}
}
///|
pub impl SqlDecode for Int with fn decode(v, k) {
match v {
VInt(n) => n.to_int()
_ => raise TypeMismatch(column=k, expected="Int")
}
}
///|
pub impl SqlDecode for String with fn decode(v, k) {
match v {
VText(s) => s
_ => raise TypeMismatch(column=k, expected="String")
}
}
///|
/// Numbers arrive as whichever of the two numeric shapes the driver chose, so
/// an integral column read as a `Double` is accepted rather than rejected.
pub impl SqlDecode for Double with fn decode(v, k) {
match v {
VDouble(d) => d
VInt(n) => n.to_double()
_ => raise TypeMismatch(column=k, expected="Double")
}
}
///|
pub impl SqlDecode for Bool with fn decode(v, k) {
match v {
VBool(b) => b
VInt(n) => n != 0L
_ => raise TypeMismatch(column=k, expected="Bool")
}
}
///|
/// NULL is the absent value; anything else goes through `T`'s own decoder.
pub impl[T : SqlDecode] SqlDecode for T? with fn decode(v, k) {
match v {
VNull => None
other => Some(SqlDecode::decode(other, k))
}
}
///|
pub impl SqlOrd for Int
///|
pub impl SqlOrd for Int64
///|
pub impl SqlOrd for String
///|
pub impl SqlOrd for Double
///|
/// A nullable column is still orderable and comparable.
///
/// SQL happily sorts by a column that holds NULLs, so refusing `T?` here would
/// reject ordinary queries like `ORDER BY submitted_at DESC`.
pub impl[T : SqlOrd] SqlOrd for T?
///|
pub impl SqlNum for Int
///|
pub impl SqlNum for Int64
///|
pub impl SqlNum for Double