Better support for BLOBs

- Limbo command line shell supports e.g. `SELECT x'616263';`
- `EXPLAIN SELECT x'616263';` lists the opcode

Missing:

- Command line shell not entirely compatible with SQLite when blobs have
  non-printable characters in the middle (e.g. `SELECT x'610062';`)
- Python bindings not supported (incoming soon)
This commit is contained in:
Lauri Virtanen 2024-09-22 16:44:33 +03:00
parent f23c668488
commit 0597c048fc
No known key found for this signature in database
GPG key ID: 3D41BFC2B70819BA
7 changed files with 67 additions and 12 deletions

View file

@ -216,7 +216,7 @@ This document describes the SQLite compatibility status of Limbo:
| BitAnd | No |
| BitNot | No |
| BitOr | No |
| Blob | No |
| Blob | Yes |
| Checkpoint | No |
| Clear | No |
| Close | No |

View file

@ -275,7 +275,9 @@ fn query(
Value::Integer(i) => print!("{}", i),
Value::Float(f) => print!("{:?}", f),
Value::Text(s) => print!("{}", s),
Value::Blob(b) => print!("{:?}", b),
Value::Blob(b) => {
print!("{}", String::from_utf8_lossy(b))
}
}
}
println!();
@ -305,7 +307,9 @@ fn query(
Value::Integer(i) => i.to_string().cell(),
Value::Float(f) => f.to_string().cell(),
Value::Text(s) => s.cell(),
Value::Blob(b) => format!("{:?}", b).cell(),
Value::Blob(b) => {
format!("{}", String::from_utf8_lossy(b)).cell()
}
})
.collect(),
);

View file

@ -1433,7 +1433,23 @@ pub fn translate_expr(
});
Ok(target_register)
}
ast::Literal::Blob(_) => todo!(),
ast::Literal::Blob(s) => {
let bytes = s
.as_bytes()
.chunks_exact(2)
.map(|pair| {
// We assume that sqlite3-parser has already validated that
// the input is valid hex string, thus unwrap is safe.
let hex_byte = std::str::from_utf8(pair).unwrap();
u8::from_str_radix(hex_byte, 16).unwrap()
})
.collect();
program.emit_insn(Insn::Blob {
value: bytes,
dest: target_register,
});
Ok(target_register)
}
ast::Literal::Keyword(_) => todo!(),
ast::Literal::Null => {
program.emit_insn(Insn::Null {

View file

@ -470,6 +470,20 @@ pub fn insn_to_str(
0,
format!("r[{}]='{}'", dest, value),
),
Insn::Blob { value, dest } => (
"Blob",
0,
*dest as i32,
0,
OwnedValue::Blob(Rc::new(value.clone())),
0,
format!(
"r[{}]={} (len={})",
dest,
String::from_utf8_lossy(value),
value.len()
),
),
Insn::RowId { cursor_id, dest } => (
"RowId",
*cursor_id as i32,

View file

@ -279,6 +279,12 @@ pub enum Insn {
dest: usize,
},
// Write a blob value into a register.
Blob {
value: Vec<u8>,
dest: usize,
},
// Read the rowid of the current row.
RowId {
cursor_id: CursorID,
@ -1074,6 +1080,10 @@ impl Program {
state.registers[*dest] = OwnedValue::Text(Rc::new(value.into()));
state.pc += 1;
}
Insn::Blob { value, dest } => {
state.registers[*dest] = OwnedValue::Blob(Rc::new(value.clone()));
state.pc += 1;
}
Insn::RowId { cursor_id, dest } => {
let cursor = cursors.get_mut(cursor_id).unwrap();
if let Some(ref rowid) = cursor.rowid()? {

View file

@ -395,14 +395,13 @@ do_execsql_test typeof-real {
SELECT typeof(1.0);
} {real}
# TODO: Uncomment when blobs are better supported
# do_execsql_test typeof-blob {
# SELECT typeof(x'61');
# } {blob}
#
# do_execsql_test typeof-blob-empty {
# SELECT typeof(x'');
# } {blob}
do_execsql_test typeof-blob {
SELECT typeof(x'61');
} {blob}
do_execsql_test typeof-blob-empty {
SELECT typeof(x'');
} {blob}
do_execsql_test typeof-sum-integer {
SELECT typeof(sum(age)) from users;

View file

@ -11,6 +11,18 @@ do_execsql_test select-const-2 {
SELECT 2
} {2}
do_execsql_test select-blob-empty {
SELECT x'';
} {}
do_execsql_test select-blob-ascii {
SELECT x'6C696D626f';
} {limbo}
do_execsql_test select-blob-emoji {
SELECT x'F09FA680';
} {🦀}
do_execsql_test select-limit-0 {
SELECT id FROM users LIMIT 0;
} {}