diff --git a/COMPAT.md b/COMPAT.md index eb702c402..730458614 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -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 | diff --git a/cli/main.rs b/cli/main.rs index 78d8eaf10..81eae98e3 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -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(), ); diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 37ff132ef..2cb41e48a 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -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 { diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 5cfa2b9f9..340624ce5 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -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, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 6a41c243f..f55ff6e71 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -279,6 +279,12 @@ pub enum Insn { dest: usize, }, + // Write a blob value into a register. + Blob { + value: Vec, + 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()? { diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index f9f15e8c3..50ae66cc3 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -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; diff --git a/testing/select.test b/testing/select.test index 3803bfba6..ced47d820 100755 --- a/testing/select.test +++ b/testing/select.test @@ -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; } {}