Select/Explain should only return the explanation (#2256)

This commit is contained in:
Emmanuel Keller 2023-07-13 11:12:34 +01:00 committed by GitHub
parent ce9430d1ac
commit ee3a1c211f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 197 additions and 201 deletions

View file

@ -89,28 +89,25 @@ impl Iterator {
// Process the query START clause // Process the query START clause
self.setup_start(&cancel_ctx, opt, txn, stm).await?; self.setup_start(&cancel_ctx, opt, txn, stm).await?;
// Process any EXPLAIN clause // Process any EXPLAIN clause
let explanation = self.output_explain(&cancel_ctx, opt, txn, stm)?; if !self.output_explain(&cancel_ctx, opt, txn, stm)? {
// Process prepared values // Process prepared values
self.iterate(&cancel_ctx, opt, txn, stm).await?; self.iterate(&cancel_ctx, opt, txn, stm).await?;
// Return any document errors // Return any document errors
if let Some(e) = self.error.take() { if let Some(e) = self.error.take() {
return Err(e); return Err(e);
} }
// Process any SPLIT clause // Process any SPLIT clause
self.output_split(ctx, opt, txn, stm).await?; self.output_split(ctx, opt, txn, stm).await?;
// Process any GROUP clause // Process any GROUP clause
self.output_group(ctx, opt, txn, stm).await?; self.output_group(ctx, opt, txn, stm).await?;
// Process any ORDER clause // Process any ORDER clause
self.output_order(ctx, opt, txn, stm).await?; self.output_order(ctx, opt, txn, stm).await?;
// Process any START clause // Process any START clause
self.output_start(ctx, opt, txn, stm).await?; self.output_start(ctx, opt, txn, stm).await?;
// Process any LIMIT clause // Process any LIMIT clause
self.output_limit(ctx, opt, txn, stm).await?; self.output_limit(ctx, opt, txn, stm).await?;
// Process any FETCH clause // Process any FETCH clause
self.output_fetch(ctx, opt, txn, stm).await?; self.output_fetch(ctx, opt, txn, stm).await?;
// Add the EXPLAIN clause to the result
if let Some(e) = explanation {
self.results.push(e);
} }
// Output the results // Output the results
Ok(mem::take(&mut self.results).into()) Ok(mem::take(&mut self.results).into())
@ -362,54 +359,49 @@ impl Iterator {
_opt: &Options, _opt: &Options,
_txn: &Transaction, _txn: &Transaction,
stm: &Statement<'_>, stm: &Statement<'_>,
) -> Result<Option<Value>, Error> { ) -> Result<bool, Error> {
Ok(if stm.explain() { if !stm.explain() {
let mut explains = Vec::with_capacity(self.entries.len()); return Ok(false);
for iter in &self.entries { }
let (operation, detail) = match iter { for iter in &self.entries {
Iterable::Value(v) => ("Iterate Value", vec![("value", v.to_owned())]), let (operation, detail) = match iter {
Iterable::Table(t) => { Iterable::Value(v) => ("Iterate Value", vec![("value", v.to_owned())]),
("Iterate Table", vec![("table", Value::from(t.0.to_owned()))]) Iterable::Table(t) => {
} ("Iterate Table", vec![("table", Value::from(t.0.to_owned()))])
Iterable::Thing(t) => { }
("Iterate Thing", vec![("thing", Value::Thing(t.to_owned()))]) Iterable::Thing(t) => {
} ("Iterate Thing", vec![("thing", Value::Thing(t.to_owned()))])
Iterable::Range(r) => { }
("Iterate Range", vec![("table", Value::from(r.tb.to_owned()))]) Iterable::Range(r) => {
} ("Iterate Range", vec![("table", Value::from(r.tb.to_owned()))])
Iterable::Edges(e) => { }
("Iterate Edges", vec![("from", Value::Thing(e.from.to_owned()))]) Iterable::Edges(e) => {
} ("Iterate Edges", vec![("from", Value::Thing(e.from.to_owned()))])
Iterable::Mergeable(t, v) => ( }
"Iterate Mergeable", Iterable::Mergeable(t, v) => (
vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())], "Iterate Mergeable",
), vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())],
Iterable::Relatable(t1, t2, t3) => ( ),
"Iterate Relatable", Iterable::Relatable(t1, t2, t3) => (
vec![ "Iterate Relatable",
("thing-1", Value::Thing(t1.to_owned())), vec![
("thing-2", Value::Thing(t2.to_owned())), ("thing-1", Value::Thing(t1.to_owned())),
("thing-3", Value::Thing(t3.to_owned())), ("thing-2", Value::Thing(t2.to_owned())),
], ("thing-3", Value::Thing(t3.to_owned())),
), ],
Iterable::Index(t, p) => ( ),
"Iterate Index", Iterable::Index(t, p) => (
vec![("table", Value::from(t.0.to_owned())), ("plan", p.explain())], "Iterate Index",
), vec![("table", Value::from(t.0.to_owned())), ("plan", p.explain())],
}; ),
let explain = Object::from(HashMap::from([ };
("operation", Value::from(operation)), let explain = Object::from(HashMap::from([
("detail", Value::Object(Object::from(HashMap::from_iter(detail)))), ("operation", Value::from(operation)),
])); ("detail", Value::Object(Object::from(HashMap::from_iter(detail)))),
explains.push(Value::Object(explain)); ]));
} self.results.push(Value::Object(explain));
Some(Value::Object(Object::from(HashMap::from([( }
"explain", Ok(true)
Value::Array(Array::from(explains)),
)]))))
} else {
None
})
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]

View file

@ -11,12 +11,13 @@ async fn select_where_matches_using_index() -> Result<(), Error> {
CREATE blog:1 SET title = 'Hello World!'; CREATE blog:1 SET title = 'Hello World!';
DEFINE ANALYZER simple TOKENIZERS blank,class; DEFINE ANALYZER simple TOKENIZERS blank,class;
DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25 HIGHLIGHTS; DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25 HIGHLIGHTS;
SELECT id, search::highlight('<em>', '</em>', 1) AS title FROM blog WHERE title @1@ 'Hello' EXPLAIN; SELECT id FROM blog WHERE title @1@ 'Hello' EXPLAIN;
SELECT id, search::highlight('<em>', '</em>', 1) AS title FROM blog WHERE title @1@ 'Hello';
"; ";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 4); assert_eq!(res.len(), 5);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
@ -24,13 +25,6 @@ async fn select_where_matches_using_index() -> Result<(), Error> {
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{
id: blog:1,
title: '<em>Hello</em> World!'
},
{
explain:
[
{ {
detail: { detail: {
plan: { plan: {
@ -42,7 +36,15 @@ async fn select_where_matches_using_index() -> Result<(), Error> {
}, },
operation: 'Iterate Index' operation: 'Iterate Index'
} }
] ]",
);
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: blog:1,
title: '<em>Hello</em> World!'
} }
]", ]",
); );
@ -57,34 +59,36 @@ async fn select_where_matches_without_using_index_iterator() -> Result<(), Error
CREATE blog:2 SET title = 'Foo Bar!'; CREATE blog:2 SET title = 'Foo Bar!';
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase; DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) HIGHLIGHTS; DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) HIGHLIGHTS;
SELECT id,search::highlight('<em>', '</em>', 1) AS title FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99) EXPLAIN; SELECT id FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99) EXPLAIN;
SELECT id,search::highlight('<em>', '</em>', 1) AS title FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99);
"; ";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 5); assert_eq!(res.len(), 6);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
table: 'blog',
},
operation: 'Iterate Table'
}
]",
);
assert_eq!(tmp, val);
let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
id: blog:1, id: blog:1,
title: 'Hello <em>World</em>!' title: 'Hello <em>World</em>!'
},
{
explain:
[
{
detail: {
table: 'blog',
},
operation: 'Iterate Table'
}
]
} }
]", ]",
); );
@ -93,41 +97,32 @@ async fn select_where_matches_without_using_index_iterator() -> Result<(), Error
} }
async fn select_where_matches_using_index_and_arrays(parallel: bool) -> Result<(), Error> { async fn select_where_matches_using_index_and_arrays(parallel: bool) -> Result<(), Error> {
let p = if parallel {
"PARALLEL"
} else {
""
};
let sql = format!( let sql = format!(
r" r"
CREATE blog:1 SET content = ['Hello World!', 'Be Bop', 'Foo Bãr']; CREATE blog:1 SET content = ['Hello World!', 'Be Bop', 'Foo Bãr'];
DEFINE ANALYZER simple TOKENIZERS blank,class; DEFINE ANALYZER simple TOKENIZERS blank,class;
DEFINE INDEX blog_content ON blog FIELDS content SEARCH ANALYZER simple BM25 HIGHLIGHTS; DEFINE INDEX blog_content ON blog FIELDS content SEARCH ANALYZER simple BM25 HIGHLIGHTS;
SELECT id, search::highlight('<em>', '</em>', 1) AS content FROM blog WHERE content @1@ 'Hello Bãr' {} EXPLAIN; SELECT id FROM blog WHERE content @1@ 'Hello Bãr' {p} EXPLAIN;
", SELECT id, search::highlight('<em>', '</em>', 1) AS content FROM blog WHERE content @1@ 'Hello Bãr' {p};
if parallel { "
"PARALLEL"
} else {
""
}
); );
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(&sql, &ses, None).await?; let res = &mut dbs.execute(&sql, &ses, None).await?;
assert_eq!(res.len(), 4); assert_eq!(res.len(), 5);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
//
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{
id: blog:1,
content: [
'<em>Hello</em> World!',
'Be Bop',
'Foo <em>Bãr</em>'
]
},
{
explain:
[
{ {
detail: { detail: {
plan: { plan: {
@ -139,6 +134,19 @@ async fn select_where_matches_using_index_and_arrays(parallel: bool) -> Result<(
}, },
operation: 'Iterate Index' operation: 'Iterate Index'
} }
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: blog:1,
content: [
'<em>Hello</em> World!',
'Be Bop',
'Foo <em>Bãr</em>'
] ]
} }
]", ]",
@ -164,7 +172,7 @@ async fn select_where_matches_using_index_offsets() -> Result<(), Error> {
DEFINE ANALYZER simple TOKENIZERS blank,class; DEFINE ANALYZER simple TOKENIZERS blank,class;
DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) HIGHLIGHTS; DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) HIGHLIGHTS;
DEFINE INDEX blog_content ON blog FIELDS content SEARCH ANALYZER simple BM25 HIGHLIGHTS; DEFINE INDEX blog_content ON blog FIELDS content SEARCH ANALYZER simple BM25 HIGHLIGHTS;
SELECT id, search::offsets(0) AS title, search::offsets(1) AS content FROM blog WHERE title @0@ 'title' AND content @1@ 'Hello Bãr' EXPLAIN; SELECT id, search::offsets(0) AS title, search::offsets(1) AS content FROM blog WHERE title @0@ 'title' AND content @1@ 'Hello Bãr';
"; ";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
@ -186,22 +194,6 @@ async fn select_where_matches_using_index_offsets() -> Result<(), Error> {
0: [{s:0, e:5}], 0: [{s:0, e:5}],
2: [{s:4, e:7}] 2: [{s:4, e:7}]
} }
},
{
explain:
[
{
detail: {
plan: {
index: 'blog_content',
operator: '@1@',
value: 'Hello Bãr'
},
table: 'blog',
},
operation: 'Iterate Index'
}
]
} }
]", ]",
); );

View file

@ -266,11 +266,12 @@ async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
DEFINE INDEX author ON TABLE post COLUMNS author; DEFINE INDEX author ON TABLE post COLUMNS author;
CREATE post:1 SET author = person:tobie; CREATE post:1 SET author = person:tobie;
CREATE post:2 SET author = person:tobie; CREATE post:2 SET author = person:tobie;
SELECT * FROM post WHERE author = person:tobie EXPLAIN;"; SELECT * FROM post WHERE author = person:tobie EXPLAIN;
SELECT * FROM post WHERE author = person:tobie;";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 5); assert_eq!(res.len(), 6);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
@ -278,6 +279,24 @@ async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
// //
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
plan: {
index: 'author',
operator: '=',
value: person:tobie
},
table: 'post',
},
operation: 'Iterate Index'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
@ -287,22 +306,6 @@ async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
{ {
author: person:tobie, author: person:tobie,
id: post:2 id: post:2
},
{
explain:
[
{
detail: {
plan: {
index: 'author',
operator: '=',
value: person:tobie
},
table: 'post',
},
operation: 'Iterate Index'
}
]
} }
]", ]",
); );
@ -316,37 +319,40 @@ async fn select_where_and_with_index() -> Result<(), Error> {
CREATE person:tobie SET name = 'Tobie', genre='m'; CREATE person:tobie SET name = 'Tobie', genre='m';
CREATE person:jaime SET name = 'Jaime', genre='m'; CREATE person:jaime SET name = 'Jaime', genre='m';
DEFINE INDEX person_name ON TABLE person COLUMNS name; DEFINE INDEX person_name ON TABLE person COLUMNS name;
SELECT name FROM person WHERE name = 'Tobie' AND genre = 'm' EXPLAIN;"; SELECT name FROM person WHERE name = 'Tobie' AND genre = 'm' EXPLAIN;
SELECT name FROM person WHERE name = 'Tobie' AND genre = 'm';";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 4); assert_eq!(res.len(), 5);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
// //
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
plan: {
index: 'person_name',
operator: '=',
value: 'Tobie'
},
table: 'person',
},
operation: 'Iterate Index'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
name: 'Tobie' name: 'Tobie'
},
{
explain:
[
{
detail: {
plan: {
index: 'person_name',
operator: '=',
value: 'Tobie'
},
table: 'person',
},
operation: 'Iterate Index'
}
]
} }
]", ]",
); );
@ -360,37 +366,40 @@ async fn select_where_and_with_unique_index() -> Result<(), Error> {
CREATE person:tobie SET name = 'Tobie', genre='m'; CREATE person:tobie SET name = 'Tobie', genre='m';
CREATE person:jaime SET name = 'Jaime', genre='m'; CREATE person:jaime SET name = 'Jaime', genre='m';
DEFINE INDEX person_name ON TABLE person COLUMNS name UNIQUE; DEFINE INDEX person_name ON TABLE person COLUMNS name UNIQUE;
SELECT name FROM person WHERE name = 'Jaime' AND genre = 'm' EXPLAIN;"; SELECT name FROM person WHERE name = 'Jaime' AND genre = 'm' EXPLAIN;
SELECT name FROM person WHERE name = 'Jaime' AND genre = 'm';";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 4); assert_eq!(res.len(), 5);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
// //
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
plan: {
index: 'person_name',
operator: '=',
value: 'Jaime'
},
table: 'person',
},
operation: 'Iterate Index'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
name: 'Jaime' name: 'Jaime'
},
{
explain:
[
{
detail: {
plan: {
index: 'person_name',
operator: '=',
value: 'Jaime'
},
table: 'person',
},
operation: 'Iterate Index'
}
]
} }
]", ]",
); );
@ -405,11 +414,12 @@ async fn select_where_and_with_fulltext_index() -> Result<(), Error> {
CREATE person:jaime SET name = 'Jaime', genre='m'; CREATE person:jaime SET name = 'Jaime', genre='m';
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase; DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
DEFINE INDEX ft_name ON TABLE person COLUMNS name SEARCH ANALYZER simple BM25(1.2,0.75); DEFINE INDEX ft_name ON TABLE person COLUMNS name SEARCH ANALYZER simple BM25(1.2,0.75);
SELECT name FROM person WHERE name @@ 'Jaime' AND genre = 'm' EXPLAIN;"; SELECT name FROM person WHERE name @@ 'Jaime' AND genre = 'm' EXPLAIN;
SELECT name FROM person WHERE name @@ 'Jaime' AND genre = 'm';";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 5); assert_eq!(res.len(), 6);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
@ -417,26 +427,28 @@ async fn select_where_and_with_fulltext_index() -> Result<(), Error> {
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
// //
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
plan: {
index: 'ft_name',
operator: '@@',
value: 'Jaime'
},
table: 'person',
},
operation: 'Iterate Index'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
name: 'Jaime' name: 'Jaime'
},
{
explain:
[
{
detail: {
plan: {
index: 'ft_name',
operator: '@@',
value: 'Jaime'
},
table: 'person',
},
operation: 'Iterate Index'
}
]
} }
]", ]",
); );