mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
drop in replacement for predicate that uses ast::Expr under the hood
This commit is contained in:
parent
f230703279
commit
3e369b9dde
6 changed files with 291 additions and 9 deletions
|
@ -1,5 +0,0 @@
|
|||
use limbo_sqlite3_parser::ast;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct Predicate(ast::Expr);
|
|
@ -5,6 +5,7 @@ pub(crate) use create_index::CreateIndex;
|
|||
pub(crate) use delete::Delete;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use insert::Insert;
|
||||
use limbo_sqlite3_parser::to_sql_string::ToSqlContext;
|
||||
pub(crate) use select::Select;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use update::Update;
|
||||
|
@ -15,8 +16,8 @@ pub mod create;
|
|||
pub mod create_index;
|
||||
pub mod delete;
|
||||
pub mod drop;
|
||||
pub mod expr;
|
||||
pub mod insert;
|
||||
pub mod predicate;
|
||||
pub mod select;
|
||||
pub mod update;
|
||||
|
||||
|
@ -86,3 +87,20 @@ impl Display for Query {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to print sql strings that already have all the context it needs
|
||||
struct EmptyContext;
|
||||
|
||||
impl ToSqlContext for EmptyContext {
|
||||
fn get_column_name(
|
||||
&self,
|
||||
_table_id: limbo_sqlite3_parser::ast::TableInternalId,
|
||||
_col_idx: usize,
|
||||
) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn get_table_name(&self, _id: limbo_sqlite3_parser::ast::TableInternalId) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
|
92
simulator/model/query/predicate.rs
Normal file
92
simulator/model/query/predicate.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use limbo_sqlite3_parser::{ast, to_sql_string::ToSqlString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model::{
|
||||
query::EmptyContext,
|
||||
table::{Table, Value},
|
||||
};
|
||||
|
||||
macro_rules! assert_implemented_predicate_expr {
|
||||
($val:expr) => {
|
||||
assert!(matches!(
|
||||
$val,
|
||||
ast::Expr::DoublyQualified(..) | ast::Expr::Qualified(..) | ast::Expr::Literal(..)
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Predicate(ast::Expr);
|
||||
|
||||
impl Predicate {
|
||||
pub(crate) fn true_() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Numeric("1".to_string())))
|
||||
}
|
||||
|
||||
pub(crate) fn false_() -> Self {
|
||||
Self(ast::Expr::Literal(ast::Literal::Numeric("0".to_string())))
|
||||
}
|
||||
|
||||
pub(crate) fn test(&self, row: &[Value], table: &Table) -> bool {
|
||||
match &self.0 {
|
||||
ast::Expr::Binary(lhs, operator, rhs) => {
|
||||
let lhs = expr_to_value(lhs, row, table);
|
||||
let rhs = expr_to_value(rhs, row, table);
|
||||
match (lhs, rhs) {
|
||||
(Some(lhs), Some(rhs)) => lhs.binary_compare(&rhs, *operator),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
ast::Expr::Like {
|
||||
lhs,
|
||||
not,
|
||||
op,
|
||||
rhs,
|
||||
escape: _, // TODO: support escape
|
||||
} => {
|
||||
let lhs = expr_to_value(lhs, row, table);
|
||||
let rhs = expr_to_value(rhs, row, table);
|
||||
let res = match (lhs, rhs) {
|
||||
(Some(lhs), Some(rhs)) => lhs.like_compare(&rhs, *op),
|
||||
_ => false,
|
||||
};
|
||||
if *not {
|
||||
!res
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
ast::Expr::Literal(literal) => Value::from(literal).into_bool(),
|
||||
ast::Expr::Unary(unary_operator, expr) => todo!(),
|
||||
expr => unimplemented!("{:?}", expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: In the future pass a Vec<Table> to support resolving a value from another table
|
||||
// This function attempts to convert an simpler easily computable expression into values
|
||||
// TODO: In the future, we can try to expand this computation if we want to support harder properties that require us
|
||||
// to already know more values before hand
|
||||
fn expr_to_value(expr: &ast::Expr, row: &[Value], table: &Table) -> Option<Value> {
|
||||
assert_implemented_predicate_expr!(expr);
|
||||
match expr {
|
||||
ast::Expr::DoublyQualified(_, _, col_name) | ast::Expr::Qualified(_, col_name) => table
|
||||
.columns
|
||||
.iter()
|
||||
.zip(row.iter())
|
||||
.find(|(column, _)| column.name == col_name.0)
|
||||
.map(|(_, value)| value)
|
||||
.cloned(),
|
||||
ast::Expr::Literal(literal) => Some(literal.into()),
|
||||
// TODO: add binary and unary
|
||||
_ => unreachable!("{:?}", expr),
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Predicate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_sql_string(&EmptyContext))
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
use std::{fmt::Display, ops::Deref};
|
||||
|
||||
use limbo_core::numeric::{nonnan::NonNan, Numeric};
|
||||
use limbo_sqlite3_parser::ast;
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(crate) struct Name(pub(crate) String);
|
||||
|
@ -111,3 +114,177 @@ impl Display for Value {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub const FALSE: Self = Value::Integer(0);
|
||||
pub const TRUE: Self = Value::Integer(1);
|
||||
|
||||
pub fn into_bool(&self) -> bool {
|
||||
match Numeric::from(self).try_into_bool() {
|
||||
None => false, // Value::Null
|
||||
Some(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support more predicates
|
||||
pub fn binary_compare(&self, other: &Self, operator: ast::Operator) -> bool {
|
||||
match operator {
|
||||
ast::Operator::Add => todo!(),
|
||||
ast::Operator::And => self.into_bool() && other.into_bool(),
|
||||
ast::Operator::ArrowRight => todo!(),
|
||||
ast::Operator::ArrowRightShift => todo!(),
|
||||
ast::Operator::BitwiseAnd => todo!(),
|
||||
ast::Operator::BitwiseOr => todo!(),
|
||||
ast::Operator::BitwiseNot => todo!(),
|
||||
ast::Operator::Concat => todo!(),
|
||||
ast::Operator::Equals => self == other,
|
||||
ast::Operator::Divide => todo!(),
|
||||
ast::Operator::Greater => self > other,
|
||||
ast::Operator::GreaterEquals => self >= other,
|
||||
ast::Operator::Is => todo!(),
|
||||
ast::Operator::IsNot => todo!(),
|
||||
ast::Operator::LeftShift => todo!(),
|
||||
ast::Operator::Less => self < other,
|
||||
ast::Operator::LessEquals => self <= other,
|
||||
ast::Operator::Modulus => todo!(),
|
||||
ast::Operator::Multiply => todo!(),
|
||||
ast::Operator::NotEquals => self != other,
|
||||
ast::Operator::Or => self.into_bool() || other.into_bool(),
|
||||
ast::Operator::RightShift => todo!(),
|
||||
ast::Operator::Subtract => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support more operators. Copy the implementation for exec_glob
|
||||
pub fn like_compare(&self, other: &Self, operator: ast::LikeOperator) -> bool {
|
||||
match operator {
|
||||
ast::LikeOperator::Glob => todo!(),
|
||||
ast::LikeOperator::Like => {
|
||||
// TODO: support ESCAPE `expr` option in AST
|
||||
exec_like(self.to_string().as_str(), other.to_string().as_str())
|
||||
}
|
||||
ast::LikeOperator::Match => todo!(),
|
||||
ast::LikeOperator::Regexp => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is a duplication of the exec_like function in core/vdbe/mod.rs at commit 9b9d5f9b4c9920e066ef1237c80878f4c3968524
|
||||
/// Any updates to the original function should be reflected here, otherwise the test will be incorrect.
|
||||
fn construct_like_regex(pattern: &str) -> Regex {
|
||||
let mut regex_pattern = String::with_capacity(pattern.len() * 2);
|
||||
|
||||
regex_pattern.push('^');
|
||||
|
||||
for c in pattern.chars() {
|
||||
match c {
|
||||
'\\' => regex_pattern.push_str("\\\\"),
|
||||
'%' => regex_pattern.push_str(".*"),
|
||||
'_' => regex_pattern.push('.'),
|
||||
ch => {
|
||||
if regex_syntax::is_meta_character(c) {
|
||||
regex_pattern.push('\\');
|
||||
}
|
||||
regex_pattern.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
regex_pattern.push('$');
|
||||
|
||||
RegexBuilder::new(®ex_pattern)
|
||||
.case_insensitive(true)
|
||||
.dot_matches_new_line(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn exec_like(pattern: &str, text: &str) -> bool {
|
||||
let re = construct_like_regex(pattern);
|
||||
re.is_match(text)
|
||||
}
|
||||
|
||||
impl From<&ast::Literal> for Value {
|
||||
fn from(value: &ast::Literal) -> Self {
|
||||
match value {
|
||||
ast::Literal::Null => Self::Null,
|
||||
ast::Literal::Numeric(number) => Numeric::from(number).into(),
|
||||
ast::Literal::String(string) => Self::Text(string.clone()),
|
||||
ast::Literal::Blob(blob) => Self::Blob(
|
||||
blob.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(),
|
||||
),
|
||||
lit => unimplemented!("{:?}", lit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::Literal> for Value {
|
||||
fn from(value: ast::Literal) -> Self {
|
||||
match value {
|
||||
ast::Literal::Null => Self::Null,
|
||||
ast::Literal::Numeric(number) => Numeric::from(number).into(),
|
||||
ast::Literal::String(string) => Self::Text(string),
|
||||
ast::Literal::Blob(blob) => Self::Blob(
|
||||
blob.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(),
|
||||
),
|
||||
lit => unimplemented!("{:?}", lit),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for Value {
|
||||
fn from(value: Numeric) -> Self {
|
||||
match value {
|
||||
Numeric::Null => Value::Null,
|
||||
Numeric::Integer(i) => Value::Integer(i),
|
||||
Numeric::Float(f) => Value::Float(f.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from numeric in Core
|
||||
impl From<Value> for Numeric {
|
||||
fn from(value: Value) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Value> for Numeric {
|
||||
fn from(value: &Value) -> Self {
|
||||
match value {
|
||||
Value::Null => Self::Null,
|
||||
Value::Integer(v) => Self::Integer(*v),
|
||||
Value::Float(v) => match NonNan::new(*v) {
|
||||
Some(v) => Self::Float(v),
|
||||
None => Self::Null,
|
||||
},
|
||||
Value::Text(text) => Numeric::from(text.as_str()),
|
||||
Value::Blob(blob) => {
|
||||
let text = String::from_utf8_lossy(blob.as_slice());
|
||||
Numeric::from(&text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
fn from(value: bool) -> Self {
|
||||
value.then_some(Value::TRUE).unwrap_or(Value::FALSE)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue