diff --git a/README.md b/README.md index e3f959502..839821afe 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,12 @@ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid cargo bench --bench benchmark -- --profile-time=5 ``` +## FAQ + +### How is Limbo different from libSQL? + +Limbo is a research project to build a SQLite compatible in-process database in Rust with native async support. The libSQL project, on the other hand, is an open source, open contribution fork of SQLite, with focus on production features such as replication, backups, encryption, and so on. There is no hard dependency between the two projects. Of course, if Limbo becomes widely successful, we might consider merging with libSQL, but that is something that will be decided in the future. + ## Publications * Pekka Enberg, Sasu Tarkoma, Jon Crowcroft Ashwin Rao (2024). Serverless Runtime / Database Co-Design With Asynchronous I/O. In _EdgeSys ‘24_. [[PDF]](https://penberg.org/papers/penberg-edgesys24.pdf) diff --git a/core/translate.rs b/core/translate.rs index 5e2afaa75..0d9d73b5d 100644 --- a/core/translate.rs +++ b/core/translate.rs @@ -306,7 +306,7 @@ fn emit_limit_insn(limit_info: &Option, program: &mut ProgramBuilder) fn translate_where(select: &Select, program: &mut ProgramBuilder) -> Result> { if let Some(w) = &select.where_clause { let label = program.allocate_label(); - translate_condition_expr(program, select, w, label)?; + translate_condition_expr(program, select, w, label, false)?; Ok(Some(label)) } else { Ok(None) @@ -339,8 +339,8 @@ fn translate_conditions( ast::JoinConstraint::On(expr) => { // Combine where clause and join condition let label = program.allocate_label(); - translate_condition_expr(program, select, where_clause, label)?; - translate_condition_expr(program, select, expr, label)?; + translate_condition_expr(program, select, where_clause, label, false)?; + translate_condition_expr(program, select, expr, label, false)?; Ok(Some(label)) } ast::JoinConstraint::Using(_) => { @@ -351,13 +351,13 @@ fn translate_conditions( (None, None) => Ok(None), (Some(where_clause), None) => { let label = program.allocate_label(); - translate_condition_expr(program, select, where_clause, label)?; + translate_condition_expr(program, select, where_clause, label, false)?; Ok(Some(label)) } (None, Some(join)) => match join { ast::JoinConstraint::On(expr) => { let label = program.allocate_label(); - translate_condition_expr(program, select, expr, label)?; + translate_condition_expr(program, select, expr, label, false)?; Ok(Some(label)) } ast::JoinConstraint::Using(_) => { @@ -568,7 +568,8 @@ fn translate_condition_expr( program: &mut ProgramBuilder, select: &Select, expr: &ast::Expr, - jump_target: BranchOffset, + target_jump: BranchOffset, + jump_if_true: bool, // if true jump to target on op == true, if false invert op ) -> Result<()> { match expr { ast::Expr::Binary(e1, op, e2) => match op { @@ -591,56 +592,166 @@ fn translate_condition_expr_leaf( ) -> Result<()> { match expr { ast::Expr::Between { .. } => todo!(), - ast::Expr::Binary(e1, op, e2) => { - let e1_reg = program.alloc_register(); - let e2_reg = program.alloc_register(); - let _ = translate_expr(program, select, e1, e1_reg)?; - match e1.as_ref() { - ast::Expr::Literal(_) => program.mark_last_insn_constant(), - _ => {} + ast::Expr::Binary(lhs, ast::Operator::And, rhs) => { + if jump_if_true { + let label = program.allocate_label(); + let _ = translate_condition_expr(program, select, lhs, label, false); + let _ = translate_condition_expr(program, select, rhs, target_jump, true); + program.resolve_label(label, program.offset()); + } else { + let _ = translate_condition_expr(program, select, lhs, target_jump, false); + let _ = translate_condition_expr(program, select, rhs, target_jump, false); } - let _ = translate_expr(program, select, e2, e2_reg)?; - match e2.as_ref() { - ast::Expr::Literal(_) => program.mark_last_insn_constant(), - _ => {} + } + ast::Expr::Binary(lhs, ast::Operator::Or, rhs) => { + if jump_if_true { + let _ = translate_condition_expr(program, select, lhs, target_jump, true); + let _ = translate_condition_expr(program, select, rhs, target_jump, true); + } else { + let label = program.allocate_label(); + let _ = translate_condition_expr(program, select, lhs, label, true); + let _ = translate_condition_expr(program, select, rhs, target_jump, false); + program.resolve_label(label, program.offset()); } - if jump_target < 0 { - program.add_label_dependency(jump_target, program.offset()); + } + ast::Expr::Binary(lhs, op, rhs) => { + let lhs_reg = program.alloc_register(); + let rhs_reg = program.alloc_register(); + let _ = translate_expr(program, select, lhs, lhs_reg); + let _ = translate_expr(program, select, rhs, rhs_reg); + match op { + ast::Operator::Greater => { + if jump_if_true { + program.emit_insn_with_label_dependency( + Insn::Gt { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } else { + program.emit_insn_with_label_dependency( + Insn::Le { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } + } + ast::Operator::GreaterEquals => { + if jump_if_true { + program.emit_insn_with_label_dependency( + Insn::Ge { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } else { + program.emit_insn_with_label_dependency( + Insn::Lt { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } + } + ast::Operator::Less => { + if jump_if_true { + program.emit_insn_with_label_dependency( + Insn::Lt { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } else { + program.emit_insn_with_label_dependency( + Insn::Ge { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } + } + ast::Operator::LessEquals => { + if jump_if_true { + program.emit_insn_with_label_dependency( + Insn::Le { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } else { + program.emit_insn_with_label_dependency( + Insn::Gt { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } + } + ast::Operator::Equals => { + if jump_if_true { + program.emit_insn_with_label_dependency( + Insn::Eq { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } else { + program.emit_insn_with_label_dependency( + Insn::Ne { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } + } + ast::Operator::NotEquals => { + if jump_if_true { + program.emit_insn_with_label_dependency( + Insn::Ne { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } else { + program.emit_insn_with_label_dependency( + Insn::Eq { + lhs: lhs_reg, + rhs: rhs_reg, + target_pc: target_jump, + }, + target_jump, + ) + } + } + ast::Operator::Is => todo!(), + ast::Operator::IsNot => todo!(), + _ => { + todo!("op {:?} not implemented", op); + } } - program.emit_insn(match op { - ast::Operator::NotEquals => Insn::Eq { - lhs: e1_reg, - rhs: e2_reg, - target_pc: jump_target, - }, - ast::Operator::Equals => Insn::Ne { - lhs: e1_reg, - rhs: e2_reg, - target_pc: jump_target, - }, - ast::Operator::Less => Insn::Ge { - lhs: e1_reg, - rhs: e2_reg, - target_pc: jump_target, - }, - ast::Operator::LessEquals => Insn::Gt { - lhs: e1_reg, - rhs: e2_reg, - target_pc: jump_target, - }, - ast::Operator::Greater => Insn::Le { - lhs: e1_reg, - rhs: e2_reg, - target_pc: jump_target, - }, - ast::Operator::GreaterEquals => Insn::Lt { - lhs: e1_reg, - rhs: e2_reg, - target_pc: jump_target, - }, - other => todo!("{:?}", other), - }); - Ok(()) } ast::Expr::Literal(lit) => match lit { ast::Literal::Numeric(val) => { @@ -651,22 +762,30 @@ fn translate_condition_expr_leaf( value: int_value, dest: reg, }); - if jump_target < 0 { - program.add_label_dependency(jump_target, program.offset()); + if target_jump < 0 { + program.add_label_dependency(target_jump, program.offset()); } program.emit_insn(Insn::IfNot { reg, - target_pc: jump_target, + target_pc: target_jump, }); - Ok(()) } else { anyhow::bail!("Parse error: unsupported literal type in condition"); } } _ => todo!(), }, - _ => todo!(), + ast::Expr::InList { lhs, not, rhs } => {} + ast::Expr::Like { + lhs, + not, + op, + rhs, + escape, + } => {} + _ => todo!("op {:?} not implemented", expr), } + Ok(()) } fn wrap_eval_jump_expr( diff --git a/core/vdbe.rs b/core/vdbe.rs index e8c3d327f..d1651eba1 100644 --- a/core/vdbe.rs +++ b/core/vdbe.rs @@ -345,10 +345,6 @@ impl ProgramBuilder { ); let label_references = &mut self.unresolved_labels[label_index]; - assert!( - !label_references.is_empty(), - "Trying to resolve an empty created label, all labels must be resolved for now." - ); for insn_reference in label_references.iter() { let insn = &mut self.insns[*insn_reference]; match insn { diff --git a/testing/all.test b/testing/all.test index 76ba2367f..55baa9653 100755 --- a/testing/all.test +++ b/testing/all.test @@ -205,3 +205,51 @@ do_execsql_test select-add { select u.age + 1 from users u where u.age = 91 limit 1; } {92} +do_execsql_test select-where-and { + select first_name, age from users where first_name = 'Jamie' and age > 80 +} {Jamie|94 +Jamie|88 +Jamie|99 +Jamie|92 +Jamie|87 +Jamie|88 +} + +do_execsql_test select-where-or { + select first_name, age from users where first_name = 'Jamie' and age > 80 +} {Jamie|94 +Jamie|88 +Jamie|99 +Jamie|92 +Jamie|87 +Jamie|88 +} + +do_execsql_test select-where-and-or { + select first_name, age from users where first_name = 'Jamie' or age = 1 and age = 2 +} {Jamie|94 +Jamie|88 +Jamie|31 +Jamie|26 +Jamie|71 +Jamie|50 +Jamie|28 +Jamie|46 +Jamie|17 +Jamie|64 +Jamie|76 +Jamie|99 +Jamie|92 +Jamie|47 +Jamie|27 +Jamie|54 +Jamie|47 +Jamie|15 +Jamie|12 +Jamie|71 +Jamie|87 +Jamie|34 +Jamie|88 +Jamie|41 +Jamie|73 +}