///|
/// One decoded value in a SQLite record body.
pub(all) enum RecordValue {
Null
Integer(Int64)
Real(Double)
Text(String)
Blob(Bytes)
} derive(Eq, Debug)
///|
pub fn RecordValue::kind(self : RecordValue) -> String {
match self {
Null => "null"
Integer(_) => "integer"
Real(_) => "real"
Text(_) => "text"
Blob(_) => "blob"
}
}
///|
pub fn RecordValue::display(self : RecordValue) -> String {
match self {
Null => "NULL"
Integer(value) => value.to_string()
Real(value) => value.to_string()
Text(value) => value
Blob(value) => "x'" + bytes_hex(value) + "'"
}
}
///|
pub(all) struct RecordColumn {
index : Int
serial_type : UInt64
body_offset : Int
byte_length : Int
value : RecordValue
} derive(Eq, Debug)
///|
pub(all) struct SqliteRecord {
header_size : Int
body_offset : Int
columns : Array[RecordColumn]
trailing_bytes : Int
} derive(Eq, Debug)
///|
pub fn SqliteRecord::column_count(self : SqliteRecord) -> Int {
self.columns.length()
}
///|
pub fn SqliteRecord::value(self : SqliteRecord, index : Int) -> RecordValue? {
if index < 0 || index >= self.columns.length() {
None
} else {
Some(self.columns[index].value)
}
}
///|
/// Return the number of body bytes represented by a serial type.
pub fn serial_type_length(serial_type : UInt64) -> Int raise ParseError {
match serial_type {
0UL | 8UL | 9UL => 0
1UL => 1
2UL => 2
3UL => 3
4UL => 4
5UL => 6
6UL | 7UL => 8
10UL | 11UL =>
raise InvalidValue(0, "record serial type", serial_type.to_string())
value => {
guard value <= 0x10000000UL else {
raise IntegerOverflow("record serial type length")
}
if (value & 1UL) == 0UL {
((value - 12UL) / 2UL).to_int()
} else {
((value - 13UL) / 2UL).to_int()
}
}
}
}
///|
fn decode_record_text(bytes : Bytes, encoding : TextEncoding) -> String {
match encoding {
Utf8 | Unspecified => @utf8.decode_lossy(bytes[:])
Utf16Le => @utf16.decode_lossy(bytes[:], endianness=@utf16.Little)
Utf16Be => @utf16.decode_lossy(bytes[:], endianness=@utf16.Big)
}
}
///|
fn decode_record_value(
serial_type : UInt64,
reader : BinaryReader,
encoding : TextEncoding,
) -> RecordValue raise ParseError {
match serial_type {
0UL => Null
1UL => Integer(Int64::from_int(reader.read_i8()))
2UL => Integer(Int64::from_int(reader.read_i16_be()))
3UL => Integer(Int64::from_int(reader.read_i24_be()))
4UL => Integer(reader.read_i32_be())
5UL => Integer(reader.read_int_be(6))
6UL => Integer(reader.read_i64_be())
7UL => Real(reader.read_f64_be())
8UL => Integer(0L)
9UL => Integer(1L)
10UL | 11UL =>
raise InvalidValue(
reader.absolute_position(),
"record serial type",
serial_type.to_string(),
)
value => {
let length = serial_type_length(value)
let bytes = reader.read_bytes(length)
if (value & 1UL) == 0UL {
Blob(bytes)
} else {
Text(decode_record_text(bytes, encoding))
}
}
}
}
///|
/// Parse a complete SQLite record payload.
pub fn parse_record(
payload : Bytes,
encoding : TextEncoding,
) -> SqliteRecord raise ParseError {
let prefix = BinaryReader::named(payload, "SQLite record")
let header_varint = prefix.read_varint()
guard header_varint.value <= 0x7fffffffUL else {
raise IntegerOverflow("record header size")
}
let header_size = header_varint.value.to_int()
guard header_size >= header_varint.length && header_size <= payload.length() else {
raise InvalidValue(0, "record header size", header_size.to_string())
}
let serial_reader = BinaryReader::range(
payload,
header_varint.length,
header_size - header_varint.length,
"SQLite record serial types",
)
let serial_types : Array[UInt64] = []
while !serial_reader.is_empty() {
serial_types.push(serial_reader.read_varint().value)
}
let body = BinaryReader::range(
payload,
header_size,
payload.length() - header_size,
"SQLite record body",
)
let columns : Array[RecordColumn] = []
for index = 0; index < serial_types.length(); index = index + 1 {
let serial_type = serial_types[index]
let byte_length = serial_type_length(serial_type)
let body_offset = body.relative_position()
let value = decode_record_value(serial_type, body, encoding)
columns.push({ index, serial_type, body_offset, byte_length, value })
}
{
header_size,
body_offset: header_size,
columns,
trailing_bytes: body.remaining(),
}
}
///|
pub fn validate_record(record : SqliteRecord) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
if record.trailing_bytes > 0 {
diagnostics.push(
Diagnostic::warning(
"RECORD_TRAILING_BYTES",
"record body has " +
record.trailing_bytes.to_string() +
" unused byte(s)",
),
)
}
for column in record.columns {
if column.serial_type == 10UL || column.serial_type == 11UL {
diagnostics.push(
Diagnostic::error(
"RECORD_RESERVED_SERIAL_TYPE",
"record uses reserved serial type " + column.serial_type.to_string(),
offset=record.body_offset + column.body_offset,
),
)
}
}
diagnostics
}