select/count optimisation on table range (#4786)

This commit is contained in:
Emmanuel Keller 2024-09-17 12:50:08 +01:00 committed by GitHub
parent 7912896c9b
commit efb7000583
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 189 additions and 44 deletions

View file

@ -29,7 +29,7 @@ pub(crate) enum Iterable {
Value(Value), Value(Value),
Table(Table, bool), // true = keys only Table(Table, bool), // true = keys only
Thing(Thing), Thing(Thing),
TableRange(String, IdRange), TableRange(String, IdRange, bool), // true = keys_only
Edges(Edges), Edges(Edges),
Defer(Thing), Defer(Thing),
Mergeable(Thing, Value), Mergeable(Thing, Value),
@ -169,7 +169,7 @@ impl Iterator {
}); });
} }
_ => { _ => {
self.ingest(Iterable::TableRange(v.tb, *r.to_owned())); self.ingest(Iterable::TableRange(v.tb, *r.to_owned(), false));
} }
}; };
} }

View file

@ -110,8 +110,13 @@ impl ExplainItem {
name: "Iterate Defer".into(), name: "Iterate Defer".into(),
details: vec![("thing", Value::Thing(t.to_owned()))], details: vec![("thing", Value::Thing(t.to_owned()))],
}, },
Iterable::TableRange(tb, r) => Self { Iterable::TableRange(tb, r, keys_only) => Self {
name: "Iterate Range".into(), name: if *keys_only {
"Iterate Range Keys"
} else {
"Iterate Range"
}
.into(),
details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())], details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())],
}, },
Iterable::Edges(e) => Self { Iterable::Edges(e) => Self {

View file

@ -138,8 +138,12 @@ impl<'a> Processor<'a> {
Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?, Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?,
Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?, Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?,
Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?, Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?,
Iterable::TableRange(tb, v) => { Iterable::TableRange(tb, v, keys_only) => {
self.process_range(stk, ctx, opt, stm, tb, v).await? if keys_only {
self.process_range_keys(stk, ctx, opt, stm, &tb, v).await?
} else {
self.process_range(stk, ctx, opt, stm, &tb, v).await?
}
} }
Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?, Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?,
Iterable::Table(v, keys_only) => { Iterable::Table(v, keys_only) => {
@ -388,39 +392,50 @@ impl<'a> Processor<'a> {
Ok(()) Ok(())
} }
async fn process_range( async fn process_range_prepare(
&mut self, txn: &Transaction,
stk: &mut Stk,
ctx: &Context,
opt: &Options, opt: &Options,
stm: &Statement<'_>, tb: &str,
tb: String,
r: IdRange, r: IdRange,
) -> Result<(), Error> { ) -> Result<(Vec<u8>, Vec<u8>), Error> {
// Get the transaction
let txn = ctx.tx();
// Check that the table exists // Check that the table exists
txn.check_ns_db_tb(opt.ns()?, opt.db()?, &tb, opt.strict).await?; txn.check_ns_db_tb(opt.ns()?, opt.db()?, tb, opt.strict).await?;
// Prepare the range start key // Prepare the range start key
let beg = match &r.beg { let beg = match &r.beg {
Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, &tb), Bound::Unbounded => thing::prefix(opt.ns()?, opt.db()?, tb),
Bound::Included(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(), Bound::Included(v) => thing::new(opt.ns()?, opt.db()?, tb, v).encode().unwrap(),
Bound::Excluded(v) => { Bound::Excluded(v) => {
let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(); let mut key = thing::new(opt.ns()?, opt.db()?, tb, v).encode().unwrap();
key.push(0x00); key.push(0x00);
key key
} }
}; };
// Prepare the range end key // Prepare the range end key
let end = match &r.end { let end = match &r.end {
Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, &tb), Bound::Unbounded => thing::suffix(opt.ns()?, opt.db()?, tb),
Bound::Excluded(v) => thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(), Bound::Excluded(v) => thing::new(opt.ns()?, opt.db()?, tb, v).encode().unwrap(),
Bound::Included(v) => { Bound::Included(v) => {
let mut key = thing::new(opt.ns()?, opt.db()?, &tb, v).encode().unwrap(); let mut key = thing::new(opt.ns()?, opt.db()?, tb, v).encode().unwrap();
key.push(0x00); key.push(0x00);
key key
} }
}; };
Ok((beg, end))
}
async fn process_range(
&mut self,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
stm: &Statement<'_>,
tb: &str,
r: IdRange,
) -> Result<(), Error> {
// Get the transaction
let txn = ctx.tx();
// Prepare
let (beg, end) = Self::process_range_prepare(&txn, opt, tb, r).await?;
// Create a new iterable range // Create a new iterable range
let mut stream = txn.stream(beg..end, None); let mut stream = txn.stream(beg..end, None);
// Loop until no more entries // Loop until no more entries
@ -448,6 +463,46 @@ impl<'a> Processor<'a> {
Ok(()) Ok(())
} }
async fn process_range_keys(
&mut self,
stk: &mut Stk,
ctx: &Context,
opt: &Options,
stm: &Statement<'_>,
tb: &str,
r: IdRange,
) -> Result<(), Error> {
// Get the transaction
let txn = ctx.tx();
// Prepare
let (beg, end) = Self::process_range_prepare(&txn, opt, tb, r).await?;
// Create a new iterable range
let mut stream = txn.stream_keys(beg..end);
// Loop until no more entries
while let Some(res) = stream.next().await {
// Check if the context is finished
if ctx.is_done() {
break;
}
// Parse the data from the store
let k = res?;
let key: thing::Thing = (&k).into();
let val = Value::Null;
let rid = Thing::from((key.tb, key.id));
// Create a new operable value
let val = Operable::Value(val.into());
// Process the record
let pro = Processed {
rid: Some(rid.into()),
ir: None,
val,
};
self.process(stk, ctx, opt, stm, pro).await?;
}
// Everything ok
Ok(())
}
async fn process_edge( async fn process_edge(
&mut self, &mut self,
stk: &mut Stk, stk: &mut Stk,

View file

@ -29,6 +29,28 @@ pub(crate) struct QueryPlannerParams<'a> {
group: Option<&'a Groups>, group: Option<&'a Groups>,
} }
impl<'a> QueryPlannerParams<'a> {
pub(crate) fn is_keys_only(&self) -> bool {
if !self.fields.is_count_all_only() {
return false;
}
if self.cond.is_some() {
return false;
}
if let Some(g) = self.group {
if !g.is_empty() {
return false;
}
}
if let Some(p) = self.order {
if !p.is_empty() {
return false;
}
}
true
}
}
impl<'a> From<&'a SelectStatement> for QueryPlannerParams<'a> { impl<'a> From<&'a SelectStatement> for QueryPlannerParams<'a> {
fn from(stmt: &'a SelectStatement) -> Self { fn from(stmt: &'a SelectStatement) -> Self {
QueryPlannerParams { QueryPlannerParams {

View file

@ -47,7 +47,7 @@ impl PlanBuilder {
}; };
// If we only count and there are no conditions and no aggregations, then we can only scan keys // If we only count and there are no conditions and no aggregations, then we can only scan keys
let keys_only = Self::is_keys_only(params); let keys_only = params.is_keys_only();
if let Some(With::NoIndex) = params.with { if let Some(With::NoIndex) = params.with {
return Ok(Self::table_iterator(Some("WITH NOINDEX"), keys_only)); return Ok(Self::table_iterator(Some("WITH NOINDEX"), keys_only));
@ -93,26 +93,6 @@ impl PlanBuilder {
Ok(Self::table_iterator(None, keys_only)) Ok(Self::table_iterator(None, keys_only))
} }
fn is_keys_only(p: &QueryPlannerParams) -> bool {
if !p.fields.is_count_all_only() {
return false;
}
if p.cond.is_some() {
return false;
}
if let Some(g) = p.group {
if !g.is_empty() {
return false;
}
}
if let Some(p) = p.order {
if !p.is_empty() {
return false;
}
}
true
}
fn table_iterator(reason: Option<&str>, keys_only: bool) -> Plan { fn table_iterator(reason: Option<&str>, keys_only: bool) -> Plan {
let reason = reason.map(|s| s.to_string()); let reason = reason.map(|s| s.to_string());
Plan::TableIterator(reason, keys_only) Plan::TableIterator(reason, keys_only)

View file

@ -112,7 +112,9 @@ impl SelectStatement {
planner.add_iterables(stk, &ctx, &opt, t, &params, &mut i).await?; planner.add_iterables(stk, &ctx, &opt, t, &params, &mut i).await?;
} }
Value::Thing(v) => match &v.id { Value::Thing(v) => match &v.id {
Id::Range(r) => i.ingest(Iterable::TableRange(v.tb, *r.to_owned())), Id::Range(r) => {
i.ingest(Iterable::TableRange(v.tb, *r.to_owned(), params.is_keys_only()))
}
_ => i.ingest(Iterable::Thing(v)), _ => i.ingest(Iterable::Thing(v)),
}, },
Value::Edges(v) => { Value::Edges(v) => {

View file

@ -814,3 +814,84 @@ async fn select_count_group_all() -> Result<(), Error> {
)?; )?;
Ok(()) Ok(())
} }
#[tokio::test]
async fn select_count_range_keys_only() -> Result<(), Error> {
let sql = r#"
CREATE table:1 CONTENT { bar: "hello", foo: "Man"};
CREATE table:2 CONTENT { bar: "hello", foo: "World"};
CREATE table:3 CONTENT { bar: "world"};
SELECT COUNT() FROM table:1..4 GROUP ALL EXPLAIN;
SELECT COUNT() FROM table:1..4 GROUP ALL;
SELECT COUNT() FROM table:1..4 EXPLAIN;
SELECT COUNT() FROM table:1..4;
"#;
let mut t = Test::new(sql).await?;
t.expect_size(7)?;
//
t.skip_ok(3)?;
//
t.expect_val(
r#"[
{
detail: {
range: 1..4,
table: 'table'
},
operation: 'Iterate Range Keys'
},
{
detail: {
idioms: {
count: [
'count'
]
},
type: 'Group'
},
operation: 'Collector'
}
]"#,
)?;
//
t.expect_val(
r#"[
{
count: 3
}
]"#,
)?;
//
t.expect_val(
r#"[
{
detail: {
range: 1..4,
table: 'table'
},
operation: 'Iterate Range Keys'
},
{
detail: {
type: 'Memory'
},
operation: 'Collector'
}
]"#,
)?;
//
t.expect_val(
r#"[
{
count: 1
},
{
count: 1
},
{
count: 1
}
]"#,
)?;
Ok(())
}