coerceForColumn function
Coerce a value to the column's type for storage/comparison purposes.
Implementation
Object? coerceForColumn(Object? value, ColumnDef col, {bool strict = false}) {
if (value == null) {
if (col.notNull) {
throw FormatException('Column ${col.name} is NOT NULL');
}
return null;
}
if (strict) {
final ok = switch (col.type) {
// SQLite STRICT INTEGER accepts integer-valued REALs; we follow.
DataType.integer =>
value is int || (value is double && value == value.truncateToDouble()),
DataType.real => value is double || value is int,
DataType.text => value is String,
DataType.boolean => value is bool,
DataType.blob => value is List<int>,
DataType.numeric =>
value is num || (value is String && double.tryParse(value) != null),
DataType.any => true,
};
if (!ok) {
throw FormatException(
'STRICT: column ${col.name} expects ${col.type.name}, got ${value.runtimeType}',
);
}
// Even in STRICT we still normalize 1.0 -> 1 for INTEGER columns and
// run NUMERIC affinity, otherwise leave the value alone.
if (col.type == DataType.integer && value is double) {
return value.toInt();
}
if (col.type == DataType.numeric) {
return coerce(value, DataType.numeric);
}
return value;
}
// SQLite affinity semantics in non-strict mode:
// * BLOB affinity never converts \u2014 every value is stored verbatim.
// * INTEGER / REAL / NUMERIC affinity tries to convert; if the value
// cannot be losslessly converted (e.g. INTEGER column receiving the
// string 'abc') the original value is stored unchanged, matching
// SQLite's "no-op when conversion fails" rule.
// * TEXT and BOOLEAN affinity continue to delegate to [coerce], which
// already performs the standard conversions.
if (col.type == DataType.blob) return value;
if (col.type == DataType.integer ||
col.type == DataType.real ||
col.type == DataType.numeric) {
try {
return coerce(value, col.type);
} on FormatException {
return value;
}
}
return coerce(value, col.type);
}