diff --git a/core/translate/mod.rs b/core/translate/mod.rs index dc77a00b3..17ed9deca 100644 --- a/core/translate/mod.rs +++ b/core/translate/mod.rs @@ -42,7 +42,24 @@ pub fn translate( ast::Stmt::Begin(_, _) => bail_parse_error!("BEGIN not supported yet"), ast::Stmt::Commit(_) => bail_parse_error!("COMMIT not supported yet"), ast::Stmt::CreateIndex { .. } => bail_parse_error!("CREATE INDEX not supported yet"), - ast::Stmt::CreateTable { .. } => bail_parse_error!("CREATE TABLE not supported yet"), + ast::Stmt::CreateTable { + temporary, + if_not_exists, + tbl_name, + body, + } => { + if temporary { + bail_parse_error!("TEMPORARY table not supported yet"); + } + translate_create_table( + tbl_name, + body, + if_not_exists, + database_header, + connection, + schema, + ) + } ast::Stmt::CreateTrigger { .. } => bail_parse_error!("CREATE TRIGGER not supported yet"), ast::Stmt::CreateView { .. } => bail_parse_error!("CREATE VIEW not supported yet"), ast::Stmt::CreateVirtualTable { .. } => { @@ -85,6 +102,195 @@ pub fn translate( } } +/* Example: + +sqlite> EXPLAIN CREATE TABLE users (id INT, email TEXT);; +addr opcode p1 p2 p3 p4 p5 comment +---- ------------- ---- ---- ---- ------------- -- ------------- +0 Init 0 30 0 0 Start at 30 +1 ReadCookie 0 3 2 0 +2 If 3 5 0 0 +3 SetCookie 0 2 4 0 +4 SetCookie 0 5 1 0 +5 CreateBtree 0 2 1 0 r[2]=root iDb=0 flags=1 +6 OpenWrite 0 1 0 5 0 root=1 iDb=0 +7 NewRowid 0 1 0 0 r[1]=rowid +8 Blob 6 3 0 0 r[3]= (len=6) +9 Insert 0 3 1 8 intkey=r[1] data=r[3] +10 Close 0 0 0 0 +11 Close 0 0 0 0 +12 Null 0 4 5 0 r[4..5]=NULL +13 Noop 2 0 4 0 +14 OpenWrite 1 1 0 5 0 root=1 iDb=0; sqlite_master +15 SeekRowid 1 17 1 0 intkey=r[1] +16 Rowid 1 5 0 0 r[5]= rowid of 1 +17 IsNull 5 26 0 0 if r[5]==NULL goto 26 +18 String8 0 6 0 table 0 r[6]='table' +19 String8 0 7 0 users 0 r[7]='users' +20 String8 0 8 0 users 0 r[8]='users' +21 Copy 2 9 0 0 r[9]=r[2] +22 String8 0 10 0 CREATE TABLE users (id INT, email TEXT) 0 r[10]='CREATE TABLE users (id INT, email TEXT)' +23 MakeRecord 6 5 4 BBBDB 0 r[4]=mkrec(r[6..10]) +24 Delete 1 68 5 0 +25 Insert 1 4 5 0 intkey=r[5] data=r[4] +26 SetCookie 0 1 1 0 +27 ParseSchema 0 0 0 tbl_name='users' AND type!='trigger' 0 +28 SqlExec 1 0 0 PRAGMA "main".integrity_check('users') 0 +29 Halt 0 0 0 0 +30 Transaction 0 1 0 0 1 usesStmtJournal=1 +31 Goto 0 1 0 0 + +*/ +fn translate_create_table( + tbl_name: ast::QualifiedName, + body: ast::CreateTableBody, + if_not_exists: bool, + database_header: Rc>, + connection: Weak, + schema: &Schema, +) -> Result { + let mut program = ProgramBuilder::new(); + let parse_schema_label = program.allocate_label(); + let init_label = program.allocate_label(); + program.emit_insn_with_label_dependency( + Insn::Init { + target_pc: init_label, + }, + init_label, + ); + let start_offset = program.offset(); + // TODO: ReadCookie + // TODO: If + // TODO: SetCookie + // TODO: SetCookie + let root_reg = program.alloc_register(); + program.emit_insn(Insn::CreateBtree { + db: 0, + root: root_reg, + flags: 1, + }); + let table_id = "sqlite_schema".to_string(); + let table = schema.get_table(&table_id).unwrap(); + let table = crate::schema::Table::BTree(table.clone()); + let sqlite_schema_cursor_id = + program.alloc_cursor_id(Some(table_id.to_owned()), Some(table.to_owned())); + program.emit_insn(Insn::OpenWriteAsync { + cursor_id: sqlite_schema_cursor_id, + root_page: 1, + }); + program.emit_insn(Insn::OpenWriteAwait {}); + let rowid_reg = program.alloc_register(); + program.emit_insn(Insn::NewRowid { + cursor: sqlite_schema_cursor_id, + rowid_reg, + prev_largest_reg: 0, + }); + let blob_reg = program.alloc_register(); + program.emit_insn(Insn::Blob { + value: vec![0, 0, 0, 0, 0, 0], + dest: blob_reg, + }); + program.emit_insn(Insn::InsertAsync { + cursor: sqlite_schema_cursor_id, + key_reg: rowid_reg, + record_reg: blob_reg, + flag: 0, + }); + program.emit_insn(Insn::InsertAwait { + cursor_id: sqlite_schema_cursor_id, + }); + program.emit_insn(Insn::Close { + cursor_id: sqlite_schema_cursor_id, + }); + program.emit_insn(Insn::Close { + cursor_id: sqlite_schema_cursor_id, + }); + let null_reg_1 = program.alloc_register(); + let null_reg_2 = program.alloc_register(); + program.emit_insn(Insn::Null { + dest: null_reg_1, + dest_end: Some(null_reg_2), + }); + // TODO: Noop + let sqlite_schema_cursor_id = program.alloc_cursor_id(Some(table_id), Some(table)); + program.emit_insn(Insn::OpenWriteAsync { + cursor_id: sqlite_schema_cursor_id, + root_page: 1, + }); + program.emit_insn(Insn::OpenWriteAwait {}); + let seek_failed_label = program.allocate_label(); + program.emit_insn_with_label_dependency( + Insn::SeekRowid { + cursor_id: sqlite_schema_cursor_id, + src_reg: rowid_reg, + target_pc: seek_failed_label, + }, + seek_failed_label, + ); + let rowid_reg_2 = program.alloc_register(); + program.emit_insn(Insn::RowId { + cursor_id: sqlite_schema_cursor_id, + dest: rowid_reg_2, + }); + program.resolve_label(seek_failed_label, program.offset()); + program.emit_insn_with_label_dependency( + Insn::IsNull { + src: rowid_reg, + target_pc: parse_schema_label, + }, + parse_schema_label, + ); + let type_reg = program.alloc_register(); + program.emit_insn(Insn::String8 { + value: "table".to_string(), + dest: type_reg, + }); + let name_reg = program.alloc_register(); + program.emit_insn(Insn::String8 { + value: tbl_name.name.0.to_string(), + dest: name_reg, + }); + let tbl_name_reg = program.alloc_register(); + program.emit_insn(Insn::String8 { + value: tbl_name.name.0.to_string(), + dest: tbl_name_reg, + }); + let rootpage_reg = program.alloc_register(); + program.emit_insn(Insn::Copy { + src_reg: root_reg, + dst_reg: rootpage_reg, + amount: 1, + }); + let sql_reg = program.alloc_register(); + program.emit_insn(Insn::String8 { + value: "TODO".to_string(), + dest: sql_reg, + }); + let record_reg = program.alloc_register(); + program.emit_insn(Insn::MakeRecord { + start_reg: type_reg, + count: 4, + dest_reg: record_reg, + }); + // TODO: Delete + // TODO: Insert + program.resolve_label(parse_schema_label, program.offset()); + // TODO: SetCookie + // TODO: ParseSchema + // TODO: SqlExec + program.emit_insn(Insn::Halt { + err_code: 0, + description: String::new(), + }); + program.resolve_label(init_label, program.offset()); + program.emit_insn(Insn::Transaction { write: true }); + program.emit_constant_insns(); + program.emit_insn(Insn::Goto { + target_pc: start_offset, + }); + Ok(program.build(database_header, connection)) +} + fn translate_pragma( name: &ast::QualifiedName, body: Option, diff --git a/core/vdbe/builder.rs b/core/vdbe/builder.rs index ee2bdb613..e9d8ac953 100644 --- a/core/vdbe/builder.rs +++ b/core/vdbe/builder.rs @@ -319,6 +319,10 @@ impl ProgramBuilder { assert!(*target_pc < 0); *target_pc = to_offset; } + Insn::IsNull { src, target_pc } => { + assert!(*target_pc < 0); + *target_pc = to_offset; + } _ => { todo!("missing resolve_label for {:?}", insn); } diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index b437b9d5d..ae6f4ec7c 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -866,6 +866,33 @@ pub fn insn_to_str( 0, format!("r[{}]=r[{}]", dst_reg, src_reg), ), + Insn::CreateBtree { db, root, flags } => ( + "CreateBtree", + *db as i32, + *root as i32, + *flags as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}]=root iDb={} flags={}", root, db, flags), + ), + Insn::Close { cursor_id } => ( + "Close", + *cursor_id as i32, + 0, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + "".to_string(), + ), + Insn::IsNull { src, target_pc } => ( + "IsNull", + *src as i32, + *target_pc as i32, + 0, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("if (r[{}]==NULL) goto {}", src, target_pc), + ), }; format!( "{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}", diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 322165147..3d15c5498 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -469,6 +469,30 @@ pub enum Insn { dst_reg: usize, amount: usize, // 0 amount means we include src_reg, dst_reg..=dst_reg+amount = src_reg..=src_reg+amount }, + + /// Allocate a new b-tree. + CreateBtree { + /// Allocate b-tree in main database if zero or in temp database if non-zero (P1). + db: usize, + /// The root page of the new b-tree (P2). + root: usize, + /// Flags (P3). + flags: usize, + }, + + /// Close a cursor. + Close { + cursor_id: CursorID, + }, + + /// Check if the register is null. + IsNull { + /// Source register (P1). + src: usize, + + /// Jump to this PC if the register is null (P2). + target_pc: BranchOffset, + }, } // Index of insn in list of insns @@ -2087,6 +2111,20 @@ impl Program { } state.pc += 1; } + Insn::CreateBtree { .. } => { + todo!(); + } + Insn::Close { cursor_id } => { + cursors.remove(cursor_id); + state.pc += 1; + } + Insn::IsNull { src, target_pc } => { + if matches!(state.registers[*src], OwnedValue::Null) { + state.pc = *target_pc; + } else { + state.pc += 1; + } + } } } } diff --git a/sqlite3/src/lib.rs b/sqlite3/src/lib.rs index 67c975fb6..ac37d8ea2 100644 --- a/sqlite3/src/lib.rs +++ b/sqlite3/src/lib.rs @@ -885,7 +885,7 @@ fn sqlite3_errstr_impl(rc: i32) -> *const std::ffi::c_char { "datatype mismatch", // SQLITE_MISMATCH "bad parameter or other API misuse", // SQLITE_MISUSE #[cfg(feature = "lfs")] - "", // SQLITE_NOLFS + "", // SQLITE_NOLFS #[cfg(not(feature = "lfs"))] "large file support is disabled", // SQLITE_NOLFS "authorization denied", // SQLITE_AUTH