Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion core/translate/group_by.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ use crate::translate::{
optimizer::Optimizable,
};
use crate::{
emit_explain,
schema::PseudoCursorType,
translate::collate::{get_collseq_from_expr, CollationSeq},
util::exprs_are_equivalent,
vdbe::{
builder::{CursorType, ProgramBuilder},
builder::{CursorType, ProgramBuilder, QueryMode},
insn::Insn,
BranchOffset,
},
Expand Down Expand Up @@ -156,6 +157,7 @@ pub fn init_group_by<'a>(
columns: column_count,
order_and_collations,
});
emit_explain!(program, false, "USE SORTER FOR GROUP BY".to_owned());
let pseudo_cursor = group_by_create_pseudo_table(program, column_count);
GroupByRowSource::Sorter {
pseudo_cursor,
Expand Down
8 changes: 7 additions & 1 deletion core/translate/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use super::{
},
};
use crate::{
emit_explain,
schema::{Index, Table},
translate::{
collate::{get_collseq_from_expr, resolve_comparison_collseq, CollationSeq},
Expand All @@ -37,7 +38,7 @@ use crate::{
affinity::{self, Affinity},
builder::{
CursorKey, CursorType, HashBuildSignature, MaterializedBuildInputModeTag,
ProgramBuilder,
ProgramBuilder, QueryMode,
},
insn::{to_u16, CmpInsFlags, HashBuildData, IdxInsertFlags, Insn},
BranchOffset, CursorID,
Expand Down Expand Up @@ -147,6 +148,11 @@ pub fn init_loop(
label_on_conflict: program.allocate_label(),
}),
};
emit_explain!(
program,
false,
format!("USE HASH TABLE FOR {}(DISTINCT)", agg.func)
);
}
// Include hash-join build tables so their cursors are opened for hash build.
let mut required_tables: HashSet<usize> = join_order
Expand Down
8 changes: 5 additions & 3 deletions core/translate/order_by.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,11 @@ pub fn emit_order_by(
+ if has_sequence { 1 } else { 0 }
+ remappings.iter().filter(|r| !r.deduplicated).count();

// TODO: we need to know how many indices used for sorting
// to emit correct explain output.
emit_explain!(program, false, "USE TEMP B-TREE FOR ORDER BY".to_owned());
if use_heap_sort {
emit_explain!(program, false, "USE TEMP B-TREE FOR ORDER BY".to_owned());
} else {
emit_explain!(program, false, "USE SORTER FOR ORDER BY".to_owned());
}

let cursor_id = if !use_heap_sort {
let pseudo_cursor = program.alloc_cursor_id(CursorType::Pseudo(PseudoCursorType {
Expand Down
44 changes: 37 additions & 7 deletions core/translate/subquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,21 +852,24 @@ pub fn emit_from_clause_subqueries(
}
}

// Include hash-join build tables so EXPLAIN reflects all tables that feed the loop.
let mut required_tables: HashSet<usize> = join_order
// Build the iteration order: join_order first (execution order), then any
// hash-join build tables that aren't already in the join order.
let mut visit_order: Vec<usize> = join_order
.iter()
.map(|member| member.original_idx)
.collect();
let visit_set: HashSet<usize> = visit_order.iter().copied().collect();
for table in tables.joined_tables().iter() {
if let Operation::HashJoin(hash_join_op) = &table.op {
required_tables.insert(hash_join_op.build_table_idx);
let build_idx = hash_join_op.build_table_idx;
if !visit_set.contains(&build_idx) {
visit_order.push(build_idx);
}
}
}

for (table_index, table_reference) in tables.joined_tables_mut().iter_mut().enumerate() {
if !required_tables.contains(&table_index) {
continue;
}
for table_index in visit_order {
let table_reference = &mut tables.joined_tables_mut()[table_index];
emit_explain!(
program,
true,
Expand Down Expand Up @@ -1530,6 +1533,29 @@ pub fn emit_non_from_clause_subquery(
is_correlated: bool,
) -> Result<()> {
program.nested(|program| {
let subquery_id = program.next_subquery_eqp_id();
let correlated_prefix = if is_correlated { "CORRELATED " } else { "" };
match query_type {
SubqueryType::Exists { .. } => {
// EXISTS subqueries don't get a separate EQP annotation in SQLite;
// instead the SEARCH/SCAN line gets an "EXISTS" suffix handled elsewhere.
}
SubqueryType::In { .. } => {
emit_explain!(
program,
true,
format!("{correlated_prefix}LIST SUBQUERY {subquery_id}")
);
}
SubqueryType::RowValue { .. } => {
emit_explain!(
program,
true,
format!("{correlated_prefix}SCALAR SUBQUERY {subquery_id}")
);
}
}

let label_skip_after_first_run = if !is_correlated {
let label = program.allocate_label();
program.emit_insn(Insn::Once {
Expand Down Expand Up @@ -1586,6 +1612,10 @@ pub fn emit_non_from_clause_subquery(
});
}
}
// Pop the parent explain for LIST/SCALAR SUBQUERY annotations.
if !matches!(query_type, SubqueryType::Exists { .. }) {
program.pop_current_parent_explain();
}
if let Some(label) = label_skip_after_first_run {
program.preassign_label_to_next_insn(label);
}
Expand Down
9 changes: 9 additions & 0 deletions core/vdbe/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ pub struct ProgramBuilder {
/// references in non-recursive CTEs and to prevent fallthrough to schema
/// resolution for same-named tables/views.
ctes_being_defined: Vec<String>,
/// Counter for subquery numbering in EXPLAIN QUERY PLAN output.
next_subquery_eqp_id: usize,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
Expand Down Expand Up @@ -413,9 +415,16 @@ impl ProgramBuilder {
materialized_ctes: HashMap::default(),
cte_reference_counts: HashMap::default(),
ctes_being_defined: Vec::new(),
next_subquery_eqp_id: 1,
}
}

pub fn next_subquery_eqp_id(&mut self) -> usize {
let id = self.next_subquery_eqp_id;
self.next_subquery_eqp_id += 1;
id
}

pub fn alloc_hash_table_id(&mut self) -> usize {
let id = self.next_hash_table_id;
self.next_hash_table_id = self
Expand Down
3 changes: 3 additions & 0 deletions testing/runner/src/parser/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ pub struct SnapshotCase {
pub name_span: Range<usize>,
/// SQL to execute (EXPLAIN will be prepended)
pub sql: String,
/// If true, only run EXPLAIN QUERY PLAN (no bytecode).
/// Set by the `snapshot-eqp` directive.
pub eqp_only: bool,
/// Common modifiers (setups, skip, backend, requires)
pub modifiers: CaseModifiers,
}
Expand Down
5 changes: 5 additions & 0 deletions testing/runner/src/parser/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ pub enum Token {
#[token("snapshot")]
Snapshot,

/// `snapshot-eqp` keyword (EXPLAIN QUERY PLAN only, no bytecode)
#[token("snapshot-eqp")]
SnapshotEqp,

/// `expect` keyword
#[token("expect")]
Expect,
Expand Down Expand Up @@ -233,6 +237,7 @@ impl fmt::Display for Token {
Token::Setup => write!(f, "setup"),
Token::Test => write!(f, "test"),
Token::Snapshot => write!(f, "snapshot"),
Token::SnapshotEqp => write!(f, "snapshot-eqp"),
Token::Expect => write!(f, "expect"),
Token::Error => write!(f, "error"),
Token::Pattern => write!(f, "pattern"),
Expand Down
9 changes: 6 additions & 3 deletions testing/runner/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ impl Parser {
| Token::AtRequires
| Token::AtBackend
| Token::Test
| Token::Snapshot,
| Token::Snapshot
| Token::SnapshotEqp,
) => {
// Could be test or snapshot with decorators, peek ahead
let item = self.parse_test_or_snapshot()?;
Expand Down Expand Up @@ -258,8 +259,9 @@ impl Parser {

// Now check if it's a test or snapshot
match self.peek() {
Some(Token::Snapshot) => {
self.expect_token(Token::Snapshot)?;
Some(Token::Snapshot | Token::SnapshotEqp) => {
let eqp_only = matches!(self.peek(), Some(Token::SnapshotEqp));
self.advance();
let (name, name_span) = self.expect_identifier_with_span()?;
let sql = self.expect_block_content()?.trim().to_string();

Expand All @@ -269,6 +271,7 @@ impl Parser {
name,
name_span,
sql,
eqp_only,
modifiers: CaseModifiers {
setups: test_setups,
skip,
Expand Down
47 changes: 30 additions & 17 deletions testing/runner/src/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,37 +154,40 @@ impl Runnable for SnapshotCase {
}

fn queries_to_execute(&self) -> Vec<String> {
// Run both EXPLAIN QUERY PLAN and EXPLAIN
vec![
format!("EXPLAIN QUERY PLAN {}", self.sql),
format!("EXPLAIN {}", self.sql),
]
if self.eqp_only {
vec![format!("EXPLAIN QUERY PLAN {}", self.sql)]
} else {
vec![
format!("EXPLAIN QUERY PLAN {}", self.sql),
format!("EXPLAIN {}", self.sql),
]
}
}

async fn evaluate_results(
&self,
results: Vec<QueryResult>,
options: &RunOptions,
) -> TestOutcome {
// results[0] = EXPLAIN QUERY PLAN
// results[1] = EXPLAIN
let eqp_result = results.first().expect("should have two query results");
let explain_result = results.get(1).expect("should have two query results");
let eqp_result = results.first().expect("should have EQP result");

// Check for errors in query execution
if let Some(err) = &eqp_result.error {
return TestOutcome::Error {
message: format!("EXPLAIN QUERY PLAN failed: {err}"),
};
}
if let Some(err) = &explain_result.error {
return TestOutcome::Error {
message: format!("EXPLAIN failed: {err}"),
};
}

// Format both outputs
let actual_output = format_snapshot_content(&eqp_result.rows, &explain_result.rows);
let actual_output = if self.eqp_only {
format_eqp_snapshot_content(&eqp_result.rows)
} else {
let explain_result = results.get(1).expect("should have EXPLAIN result");
if let Some(err) = &explain_result.error {
return TestOutcome::Error {
message: format!("EXPLAIN failed: {err}"),
};
}
format_snapshot_content(&eqp_result.rows, &explain_result.rows)
};

// Build snapshot info with metadata
let db_location_str = options.db_config.location.to_string();
Expand Down Expand Up @@ -223,6 +226,16 @@ impl Runnable for SnapshotCase {
}
}

/// Format EQP-only snapshot content (no bytecode).
fn format_eqp_snapshot_content(eqp_rows: &[Vec<String>]) -> String {
use crate::snapshot::format_explain_query_plan_output;

let mut output = String::new();
output.push_str("QUERY PLAN\n");
output.push_str(&format_explain_query_plan_output(eqp_rows));
output
}

/// Format the combined snapshot content with both EXPLAIN QUERY PLAN and EXPLAIN output.
fn format_snapshot_content(eqp_rows: &[Vec<String>], explain_rows: &[Vec<String>]) -> String {
use crate::snapshot::{format_explain_output, format_explain_query_plan_output};
Expand Down
18 changes: 9 additions & 9 deletions testing/runner/tests/orderby/orderby_plan.sqltest
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
@database :memory:
@skip-file-if mvcc "mvcc has slightly different cursor ids, so skipping it for now"

setup orderby_temp_btree {
setup orderby_plan {
CREATE TABLE t(a);
}

@setup orderby_temp_btree
snapshot orderby_a_uses_temp_btree {
@setup orderby_plan
snapshot orderby_a_uses_sorter {
SELECT * FROM t ORDER BY a;
}

@setup orderby_temp_btree
snapshot orderby_a_desc_uses_temp_btree {
@setup orderby_plan
snapshot orderby_a_desc_uses_sorter {
SELECT * FROM t ORDER BY a DESC;
}

@setup orderby_temp_btree
snapshot orderby_rowid_no_temp_btree {
@setup orderby_plan
snapshot orderby_rowid_no_sorter {
SELECT * FROM t ORDER BY rowid;
}

@setup orderby_temp_btree
snapshot orderby_rowid_desc_no_temp_btree {
@setup orderby_plan
snapshot orderby_rowid_desc_no_sorter {
SELECT * FROM t ORDER BY rowid DESC;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ info:
tables:
- t
setup_blocks:
- orderby_temp_btree
- orderby_plan
database: ':memory:'
---
QUERY PLAN
|--SCAN t
`--USE TEMP B-TREE FOR ORDER BY
`--USE SORTER FOR ORDER BY
BYTECODE
addr opcode p1 p2 p3 p4 p5 comment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ info:
tables:
- t
setup_blocks:
- orderby_temp_btree
- orderby_plan
database: ':memory:'
---
QUERY PLAN
|--SCAN t
`--USE TEMP B-TREE FOR ORDER BY
`--USE SORTER FOR ORDER BY
BYTECODE
addr opcode p1 p2 p3 p4 p5 comment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ info:
tables:
- t
setup_blocks:
- orderby_temp_btree
- orderby_plan
database: ':memory:'
---
QUERY PLAN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ info:
tables:
- t
setup_blocks:
- orderby_temp_btree
- orderby_plan
database: ':memory:'
---
QUERY PLAN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ info:
QUERY PLAN
|--SCAN products AS p
|--SEARCH o USING INDEX idx_orders_product
`--USE TEMP B-TREE FOR ORDER BY
|--USE SORTER FOR GROUP BY
`--USE SORTER FOR ORDER BY

BYTECODE
addr opcode p1 p2 p3 p4 p5 comment
Expand Down
Loading
Loading