mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-28 21:44:21 +01:00
Merge 'perf/prepare: box many less frequently used AST nodes' from Jussi Saurio
`sqlite3-parser` spends a lot of time in `yy_reduce` assigning different
`enum YYMINORTYPE` members, and it suffers from bad performance because
the stack size of the enum is very large, and the size of individual
members varies wildly. This PR reduces the `YYMINORTYPE` stack size from
496 to 264 bytes and has the following effect:
Preparing statement:
```sql
Prepare `SELECT 1`/Limbo/SELECT 1
time: [620.37 ns 621.08 ns 621.79 ns]
change: [-17.811% -17.582% -17.380%] (p = 0.00 < 0.05)
Performance has improved.
Prepare `SELECT * FROM users LIMIT 1`/Limbo/SELECT * FROM users LIMIT 1
time: [1.2215 µs 1.2231 µs 1.2248 µs]
change: [-12.926% -12.627% -12.272%] (p = 0.00 < 0.05)
Performance has improved.
Benchmarking Prepare `SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY cou...: Collecting 100 samples in estimated 5.0152 s (
Prepare `SELECT first_name, count(1) FROM users GROUP BY first_name HAVING count(1) > 1 ORDER BY cou...
time: [3.1056 µs 3.1096 µs 3.1138 µs]
change: [-13.279% -12.995% -12.712%] (p = 0.00 < 0.05)
Performance has improved.
```
Execute (mainly to check for regressions):
```sql
Execute `SELECT * FROM users LIMIT ?`/Limbo/1
time: [402.19 ns 402.75 ns 403.36 ns]
change: [-3.4845% -2.4003% -1.6539%] (p = 0.00 < 0.05)
Performance has improved.
Execute `SELECT * FROM users LIMIT ?`/Limbo/10
time: [2.7920 µs 2.7977 µs 2.8036 µs]
change: [-1.3135% -1.0123% -0.7132%] (p = 0.00 < 0.05)
Change within noise threshold.
Execute `SELECT * FROM users LIMIT ?`/Limbo/50
time: [13.577 µs 13.633 µs 13.690 µs]
change: [-1.7709% -1.3575% -0.9563%] (p = 0.00 < 0.05)
Change within noise threshold.
```
Closes #936
This commit is contained in:
@@ -13,7 +13,7 @@ pub fn translate_delete(
|
||||
query_mode: QueryMode,
|
||||
schema: &Schema,
|
||||
tbl_name: &QualifiedName,
|
||||
where_clause: Option<Expr>,
|
||||
where_clause: Option<Box<Expr>>,
|
||||
limit: Option<Box<Limit>>,
|
||||
syms: &SymbolTable,
|
||||
) -> Result<ProgramBuilder> {
|
||||
@@ -35,7 +35,7 @@ pub fn translate_delete(
|
||||
pub fn prepare_delete_plan(
|
||||
schema: &Schema,
|
||||
tbl_name: &QualifiedName,
|
||||
where_clause: Option<Expr>,
|
||||
where_clause: Option<Box<Expr>>,
|
||||
limit: Option<Box<Limit>>,
|
||||
) -> Result<Plan> {
|
||||
let table = match schema.get_table(tbl_name.name.0.as_str()) {
|
||||
@@ -53,7 +53,12 @@ pub fn prepare_delete_plan(
|
||||
let mut where_predicates = vec![];
|
||||
|
||||
// Parse the WHERE clause
|
||||
parse_where(where_clause, &table_references, None, &mut where_predicates)?;
|
||||
parse_where(
|
||||
where_clause.map(|e| *e),
|
||||
&table_references,
|
||||
None,
|
||||
&mut where_predicates,
|
||||
)?;
|
||||
|
||||
// Parse the LIMIT/OFFSET clause
|
||||
let (resolved_limit, resolved_offset) = limit.map_or(Ok((None, None)), |l| parse_limit(*l))?;
|
||||
|
||||
@@ -305,7 +305,7 @@ pub fn prepare_select_plan(
|
||||
exprs: group_by.exprs,
|
||||
having: if let Some(having) = group_by.having {
|
||||
let mut predicates = vec![];
|
||||
break_predicate_at_and_boundaries(having, &mut predicates);
|
||||
break_predicate_at_and_boundaries(*having, &mut predicates);
|
||||
for expr in predicates.iter_mut() {
|
||||
bind_column_references(
|
||||
expr,
|
||||
|
||||
@@ -160,19 +160,19 @@ impl Stmt {
|
||||
} => Err(custom_err!("ORDER BY without LIMIT on DELETE")),
|
||||
Self::Insert {
|
||||
columns: Some(columns),
|
||||
body: InsertBody::Select(select, ..),
|
||||
body,
|
||||
..
|
||||
} => match select.body.select.column_count() {
|
||||
ColumnCount::Fixed(n) if n != columns.len() => {
|
||||
Err(custom_err!("{} values for {} columns", n, columns.len()))
|
||||
} => match &**body {
|
||||
InsertBody::Select(select, ..) => match select.body.select.column_count() {
|
||||
ColumnCount::Fixed(n) if n != columns.len() => {
|
||||
Err(custom_err!("{} values for {} columns", n, columns.len()))
|
||||
}
|
||||
_ => Ok(()),
|
||||
},
|
||||
InsertBody::DefaultValues => {
|
||||
Err(custom_err!("0 values for {} columns", columns.len()))
|
||||
}
|
||||
_ => Ok(()),
|
||||
},
|
||||
Self::Insert {
|
||||
columns: Some(columns),
|
||||
body: InsertBody::DefaultValues,
|
||||
..
|
||||
} => Err(custom_err!("0 values for {} columns", columns.len())),
|
||||
Self::Update {
|
||||
order_by: Some(_),
|
||||
limit: None,
|
||||
|
||||
@@ -78,11 +78,11 @@ pub enum Stmt {
|
||||
Attach {
|
||||
/// filename
|
||||
// TODO distinction between ATTACH and ATTACH DATABASE
|
||||
expr: Expr,
|
||||
expr: Box<Expr>,
|
||||
/// schema name
|
||||
db_name: Expr,
|
||||
db_name: Box<Expr>,
|
||||
/// password
|
||||
key: Option<Expr>,
|
||||
key: Option<Box<Expr>>,
|
||||
},
|
||||
/// `BEGIN`: tx type, tx name
|
||||
Begin(Option<TransactionType>, Option<Name>),
|
||||
@@ -125,13 +125,13 @@ pub enum Stmt {
|
||||
/// `BEFORE`/`AFTER`/`INSTEAD OF`
|
||||
time: Option<TriggerTime>,
|
||||
/// `DELETE`/`INSERT`/`UPDATE`
|
||||
event: TriggerEvent,
|
||||
event: Box<TriggerEvent>,
|
||||
/// table name
|
||||
tbl_name: QualifiedName,
|
||||
/// `FOR EACH ROW`
|
||||
for_each_row: bool,
|
||||
/// `WHEN`
|
||||
when_clause: Option<Expr>,
|
||||
when_clause: Option<Box<Expr>>,
|
||||
/// statements
|
||||
commands: Vec<TriggerCmd>,
|
||||
},
|
||||
@@ -168,7 +168,7 @@ pub enum Stmt {
|
||||
/// `INDEXED`
|
||||
indexed: Option<Indexed>,
|
||||
/// `WHERE` clause
|
||||
where_clause: Option<Expr>,
|
||||
where_clause: Option<Box<Expr>>,
|
||||
/// `RETURNING`
|
||||
returning: Option<Vec<ResultColumn>>,
|
||||
/// `ORDER BY`
|
||||
@@ -217,7 +217,7 @@ pub enum Stmt {
|
||||
/// `COLUMNS`
|
||||
columns: Option<DistinctNames>,
|
||||
/// `VALUES` or `SELECT`
|
||||
body: InsertBody,
|
||||
body: Box<InsertBody>,
|
||||
/// `RETURNING`
|
||||
returning: Option<Vec<ResultColumn>>,
|
||||
},
|
||||
@@ -256,7 +256,7 @@ pub enum Stmt {
|
||||
/// `FROM`
|
||||
from: Option<FromClause>,
|
||||
/// `WHERE` clause
|
||||
where_clause: Option<Expr>,
|
||||
where_clause: Option<Box<Expr>>,
|
||||
/// `RETURNING`
|
||||
returning: Option<Vec<ResultColumn>>,
|
||||
/// `ORDER BY`
|
||||
@@ -1005,7 +1005,7 @@ pub struct GroupBy {
|
||||
/// expressions
|
||||
pub exprs: Vec<Expr>,
|
||||
/// `HAVING`
|
||||
pub having: Option<Expr>, // HAVING clause on a non-aggregate query
|
||||
pub having: Option<Box<Expr>>, // HAVING clause on a non-aggregate query
|
||||
}
|
||||
|
||||
/// identifier or one of several keywords or `INDEXED`
|
||||
@@ -1769,9 +1769,9 @@ pub enum TransactionType {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Upsert {
|
||||
/// conflict targets
|
||||
pub index: Option<UpsertIndex>,
|
||||
pub index: Option<Box<UpsertIndex>>,
|
||||
/// `DO` clause
|
||||
pub do_clause: UpsertDo,
|
||||
pub do_clause: Box<UpsertDo>,
|
||||
/// next upsert
|
||||
pub next: Option<Box<Upsert>>,
|
||||
}
|
||||
@@ -1872,9 +1872,9 @@ pub enum FrameBound {
|
||||
/// `CURRENT ROW`
|
||||
CurrentRow,
|
||||
/// `FOLLOWING`
|
||||
Following(Expr),
|
||||
Following(Box<Expr>),
|
||||
/// `PRECEDING`
|
||||
Preceding(Expr),
|
||||
Preceding(Box<Expr>),
|
||||
/// `UNBOUNDED FOLLOWING`
|
||||
UnboundedFollowing,
|
||||
/// `UNBOUNDED PRECEDING`
|
||||
|
||||
@@ -731,7 +731,7 @@ nulls(A) ::= . {A = None;}
|
||||
|
||||
%type groupby_opt {Option<GroupBy>}
|
||||
groupby_opt(A) ::= . {A = None;}
|
||||
groupby_opt(A) ::= GROUP BY nexprlist(X) having_opt(Y). {A = Some(GroupBy{ exprs: X, having: Y });}
|
||||
groupby_opt(A) ::= GROUP BY nexprlist(X) having_opt(Y). {A = Some(GroupBy{ exprs: X, having: Y.map(Box::new) });}
|
||||
|
||||
%type having_opt {Option<Expr>}
|
||||
having_opt(A) ::= . {A = None;}
|
||||
@@ -761,13 +761,13 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y).
|
||||
cmd ::= with(C) DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W)
|
||||
orderby_opt(O) limit_opt(L). {
|
||||
let (where_clause, returning) = W;
|
||||
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause, returning,
|
||||
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause: where_clause.map(Box::new), returning,
|
||||
order_by: O, limit: L });
|
||||
}
|
||||
%else
|
||||
cmd ::= with(C) DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W). {
|
||||
let (where_clause, returning) = W;
|
||||
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause, returning,
|
||||
self.ctx.stmt = Some(Stmt::Delete{ with: C, tbl_name: X, indexed: I, where_clause: where_clause.map(Box::new), returning,
|
||||
order_by: None, limit: None });
|
||||
}
|
||||
%endif
|
||||
@@ -791,14 +791,14 @@ cmd ::= with(C) UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from
|
||||
where_opt_ret(W) orderby_opt(O) limit_opt(L). {
|
||||
let (where_clause, returning) = W;
|
||||
self.ctx.stmt = Some(Stmt::Update { with: C, or_conflict: R, tbl_name: X, indexed: I, sets: Y, from: F,
|
||||
where_clause, returning, order_by: O, limit: L });
|
||||
where_clause: where_clause.map(Box::new), returning, order_by: O, limit: L });
|
||||
}
|
||||
%else
|
||||
cmd ::= with(C) UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
|
||||
where_opt_ret(W). {
|
||||
let (where_clause, returning) = W;
|
||||
self.ctx.stmt = Some(Stmt::Update { with: C, or_conflict: R, tbl_name: X, indexed: I, sets: Y, from: F,
|
||||
where_clause, returning, order_by: None, limit: None });
|
||||
where_clause: where_clause.map(Box::new), returning, order_by: None, limit: None });
|
||||
}
|
||||
%endif
|
||||
|
||||
@@ -828,13 +828,13 @@ cmd ::= with(W) insert_cmd(R) INTO xfullname(X) idlist_opt(F) select(S)
|
||||
let (upsert, returning) = U;
|
||||
let body = InsertBody::Select(Box::new(S), upsert);
|
||||
self.ctx.stmt = Some(Stmt::Insert{ with: W, or_conflict: R, tbl_name: X, columns: F,
|
||||
body, returning });
|
||||
body: Box::new(body), returning });
|
||||
}
|
||||
cmd ::= with(W) insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES returning(Y).
|
||||
{
|
||||
let body = InsertBody::DefaultValues;
|
||||
self.ctx.stmt = Some(Stmt::Insert{ with: W, or_conflict: R, tbl_name: X, columns: F,
|
||||
body, returning: Y });
|
||||
body: Box::new(body), returning: Y });
|
||||
}
|
||||
|
||||
%type upsert {(Option<Upsert>, Option<Vec<ResultColumn>>)}
|
||||
@@ -851,16 +851,16 @@ upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW)
|
||||
{ let index = UpsertIndex{ targets: T, where_clause: TW };
|
||||
let do_clause = UpsertDo::Set{ sets: Z, where_clause: W };
|
||||
let (next, returning) = N;
|
||||
A = (Some(Upsert{ index: Some(index), do_clause, next: next.map(Box::new) }), returning);}
|
||||
A = (Some(Upsert{ index: Some(Box::new(index)), do_clause: Box::new(do_clause), next: next.map(Box::new) }), returning);}
|
||||
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING upsert(N).
|
||||
{ let index = UpsertIndex{ targets: T, where_clause: TW };
|
||||
let (next, returning) = N;
|
||||
A = (Some(Upsert{ index: Some(index), do_clause: UpsertDo::Nothing, next: next.map(Box::new) }), returning); }
|
||||
A = (Some(Upsert{ index: Some(Box::new(index)), do_clause: Box::new(UpsertDo::Nothing), next: next.map(Box::new) }), returning); }
|
||||
upsert(A) ::= ON CONFLICT DO NOTHING returning(R).
|
||||
{ A = (Some(Upsert{ index: None, do_clause: UpsertDo::Nothing, next: None }), R); }
|
||||
{ A = (Some(Upsert{ index: None, do_clause: Box::new(UpsertDo::Nothing), next: None }), R); }
|
||||
upsert(A) ::= ON CONFLICT DO UPDATE SET setlist(Z) where_opt(W) returning(R).
|
||||
{ let do_clause = UpsertDo::Set{ sets: Z, where_clause: W };
|
||||
A = (Some(Upsert{ index: None, do_clause, next: None }), R);}
|
||||
A = (Some(Upsert{ index: None, do_clause: Box::new(do_clause), next: None }), R);}
|
||||
|
||||
%type returning {Option<Vec<ResultColumn>>}
|
||||
returning(A) ::= RETURNING selcollist(X). {A = Some(X);}
|
||||
@@ -1167,8 +1167,8 @@ minus_num(A) ::= MINUS number(X). {A = Expr::unary(UnaryOperator::Negative,
|
||||
cmd ::= createkw temp(T) TRIGGER ifnotexists(NOERR) fullname(B) trigger_time(C) trigger_event(D)
|
||||
ON fullname(E) foreach_clause(X) when_clause(G) BEGIN trigger_cmd_list(S) END. {
|
||||
self.ctx.stmt = Some(Stmt::CreateTrigger{
|
||||
temporary: T, if_not_exists: NOERR, trigger_name: B, time: C, event: D, tbl_name: E,
|
||||
for_each_row: X, when_clause: G, commands: S
|
||||
temporary: T, if_not_exists: NOERR, trigger_name: B, time: C, event: Box::new(D), tbl_name: E,
|
||||
for_each_row: X, when_clause: G.map(Box::new), commands: S
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1276,7 +1276,7 @@ cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
|
||||
//////////////////////// ATTACH DATABASE file AS name /////////////////////////
|
||||
%ifndef SQLITE_OMIT_ATTACH
|
||||
cmd ::= ATTACH database_kw_opt expr(F) AS expr(D) key_opt(K). {
|
||||
self.ctx.stmt = Some(Stmt::Attach{ expr: F, db_name: D, key: K });
|
||||
self.ctx.stmt = Some(Stmt::Attach{ expr: Box::new(F), db_name: Box::new(D), key: K.map(Box::new) });
|
||||
}
|
||||
cmd ::= DETACH database_kw_opt expr(D). {
|
||||
self.ctx.stmt = Some(Stmt::Detach(D));
|
||||
@@ -1454,9 +1454,9 @@ frame_bound_s(A) ::= UNBOUNDED PRECEDING. {A = FrameBound::UnboundedPreceding;}
|
||||
frame_bound_e(A) ::= frame_bound(X). {A = X;}
|
||||
frame_bound_e(A) ::= UNBOUNDED FOLLOWING. {A = FrameBound::UnboundedFollowing;}
|
||||
|
||||
frame_bound(A) ::= expr(X) PRECEDING. { A = FrameBound::Preceding(X); }
|
||||
frame_bound(A) ::= expr(X) PRECEDING. { A = FrameBound::Preceding(Box::new(X)); }
|
||||
frame_bound(A) ::= CURRENT ROW. { A = FrameBound::CurrentRow; }
|
||||
frame_bound(A) ::= expr(X) FOLLOWING. { A = FrameBound::Following(X); }
|
||||
frame_bound(A) ::= expr(X) FOLLOWING. { A = FrameBound::Following(Box::new(X)); }
|
||||
|
||||
%type frame_exclude_opt {Option<FrameExclude>}
|
||||
frame_exclude_opt(A) ::= . {A = None;}
|
||||
|
||||
Reference in New Issue
Block a user