mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-26 20:44:23 +01:00
Merge 'handle EXPLAIN like sqlite' from Lâm Hoàng Phúc
we are hard coding `EXPLAIN` for debugging ```sh turso> EXPLAIN SELECT 1; EXPLAIN SELECT 1; addr opcode p1 p2 p3 p4 p5 comment ---- ----------------- ---- ---- ---- ------------- -- ------- 0 Init 0 3 0 0 Start at 3 1 ResultRow 1 1 0 0 output=r[1] 2 Halt 0 0 0 0 3 Integer 1 1 0 0 r[1]=1 4 Goto 0 1 0 0 ``` ```sh sqlite> EXPLAIN SELECT 1; EXPLAIN SELECT 1; addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 4 0 0 Start at 4 1 Integer 1 1 0 0 r[1]=1 2 ResultRow 1 1 0 0 output=r[1] 3 Halt 0 0 0 0 4 Goto 0 1 0 0 addr opcode p1 p2 p3 p4 p5 comment ---- ------------- ---- ---- ---- ------------- -- ------------- 0 Init 0 4 0 0 Start at 4 1 Integer 1 1 0 0 r[1]=1 2 ResultRow 1 1 0 0 output=r[1] 3 Halt 0 0 0 0 4 Goto 0 1 0 0 ``` Closes #3005
This commit is contained in:
97
cli/app.rs
97
cli/app.rs
@@ -30,7 +30,9 @@ use std::{
|
||||
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
use turso_core::{Connection, Database, LimboError, OpenFlags, Statement, StepResult, Value};
|
||||
use turso_core::{
|
||||
Connection, Database, LimboError, OpenFlags, QueryMode, Statement, StepResult, Value,
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "Turso")]
|
||||
@@ -452,34 +454,15 @@ impl Limbo {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// TODO this is a quickfix. Some ideas to do case insensitive comparisons is to use
|
||||
// Uncased or Unicase.
|
||||
let explain_str = "explain";
|
||||
if input
|
||||
.trim_start()
|
||||
.get(..explain_str.len())
|
||||
.map(|s| s.eq_ignore_ascii_case(explain_str))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
match self.conn.query(input) {
|
||||
Ok(Some(stmt)) => {
|
||||
let _ = self.writeln(stmt.explain().as_bytes());
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = self.writeln(e.to_string());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
let conn = self.conn.clone();
|
||||
let runner = conn.query_runner(input.as_bytes());
|
||||
for output in runner {
|
||||
if self
|
||||
.print_query_result(input, output, stats.as_mut())
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
let conn = self.conn.clone();
|
||||
let runner = conn.query_runner(input.as_bytes());
|
||||
for output in runner {
|
||||
if self
|
||||
.print_query_result(input, output, stats.as_mut())
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,8 +739,56 @@ impl Limbo {
|
||||
mut statistics: Option<&mut QueryStatistics>,
|
||||
) -> anyhow::Result<()> {
|
||||
match output {
|
||||
Ok(Some(ref mut rows)) => match self.opts.output_mode {
|
||||
OutputMode::List => {
|
||||
Ok(Some(ref mut rows)) => match (self.opts.output_mode, rows.get_query_mode()) {
|
||||
(_, QueryMode::Explain) => {
|
||||
fn get_explain_indent(
|
||||
indent_count: usize,
|
||||
curr_insn: &str,
|
||||
prev_insn: &str,
|
||||
) -> usize {
|
||||
let indent_count = match prev_insn {
|
||||
"Rewind" | "Last" | "SorterSort" | "SeekGE" | "SeekGT" | "SeekLE"
|
||||
| "SeekLT" => indent_count + 1,
|
||||
_ => indent_count,
|
||||
};
|
||||
|
||||
match curr_insn {
|
||||
"Next" | "SorterNext" | "Prev" => indent_count - 1,
|
||||
_ => indent_count,
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.writeln(
|
||||
"addr opcode p1 p2 p3 p4 p5 comment",
|
||||
);
|
||||
let _ = self.writeln(
|
||||
"---- ----------------- ---- ---- ---- ------------- -- -------",
|
||||
);
|
||||
|
||||
let mut prev_insn: String = "".to_string();
|
||||
let mut indent_count = 0;
|
||||
let indent = " ";
|
||||
loop {
|
||||
row_step_result_query!(self, sql, rows, statistics, {
|
||||
let row = rows.row().unwrap();
|
||||
let insn = row.get_value(1).to_string();
|
||||
indent_count = get_explain_indent(indent_count, &insn, &prev_insn);
|
||||
let _ = self.writeln(format!(
|
||||
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",
|
||||
row.get_value(0).to_string(),
|
||||
&(indent.repeat(indent_count) + &insn),
|
||||
row.get_value(2).to_string(),
|
||||
row.get_value(3).to_string(),
|
||||
row.get_value(4).to_string(),
|
||||
row.get_value(5).to_string(),
|
||||
row.get_value(6).to_string(),
|
||||
row.get_value(7),
|
||||
));
|
||||
prev_insn = insn;
|
||||
});
|
||||
}
|
||||
}
|
||||
(OutputMode::List, _) => {
|
||||
let mut headers_printed = false;
|
||||
loop {
|
||||
row_step_result_query!(self, sql, rows, statistics, {
|
||||
@@ -788,7 +819,7 @@ impl Limbo {
|
||||
});
|
||||
}
|
||||
}
|
||||
OutputMode::Pretty => {
|
||||
(OutputMode::Pretty, _) => {
|
||||
let config = self.config.as_ref().unwrap();
|
||||
let mut table = Table::new();
|
||||
table
|
||||
@@ -837,7 +868,7 @@ impl Limbo {
|
||||
writeln!(self, "{table}")?;
|
||||
}
|
||||
}
|
||||
OutputMode::Line => {
|
||||
(OutputMode::Line, _) => {
|
||||
let mut first_row_printed = false;
|
||||
|
||||
let max_width = (0..rows.num_columns())
|
||||
|
||||
212
core/lib.rs
212
core/lib.rs
@@ -99,8 +99,8 @@ pub use types::RefValue;
|
||||
pub use types::Value;
|
||||
use util::parse_schema_rows;
|
||||
pub use util::IOExt;
|
||||
use vdbe::builder::QueryMode;
|
||||
use vdbe::builder::TableRefIdCounter;
|
||||
pub use vdbe::{builder::QueryMode, explain::EXPLAIN_COLUMNS, explain::EXPLAIN_QUERY_PLAN_COLUMNS};
|
||||
|
||||
/// Configuration for database features
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -969,21 +969,23 @@ impl Connection {
|
||||
.trim();
|
||||
self.maybe_update_schema()?;
|
||||
let pager = self.pager.borrow().clone();
|
||||
match cmd {
|
||||
Cmd::Stmt(stmt) => {
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
QueryMode::Normal,
|
||||
input,
|
||||
)?;
|
||||
Ok(Statement::new(program, self._db.mv_store.clone(), pager))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let mode = QueryMode::new(&cmd);
|
||||
let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
mode,
|
||||
input,
|
||||
)?;
|
||||
Ok(Statement::new(
|
||||
program,
|
||||
self._db.mv_store.clone(),
|
||||
pager,
|
||||
mode,
|
||||
))
|
||||
}
|
||||
|
||||
/// Parse schema from scratch if version of schema for the connection differs from the schema cookie in the root page
|
||||
@@ -1117,23 +1119,19 @@ impl Connection {
|
||||
let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])
|
||||
.unwrap()
|
||||
.trim();
|
||||
match cmd {
|
||||
Cmd::Stmt(stmt) => {
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
QueryMode::Normal,
|
||||
input,
|
||||
)?;
|
||||
|
||||
Statement::new(program, self._db.mv_store.clone(), pager.clone())
|
||||
.run_ignore_rows()?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let mode = QueryMode::new(&cmd);
|
||||
let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
mode,
|
||||
input,
|
||||
)?;
|
||||
Statement::new(program, self._db.mv_store.clone(), pager.clone(), mode)
|
||||
.run_ignore_rows()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1169,6 +1167,7 @@ impl Connection {
|
||||
}
|
||||
let syms = self.syms.borrow();
|
||||
let pager = self.pager.borrow().clone();
|
||||
let mode = QueryMode::new(&cmd);
|
||||
match cmd {
|
||||
Cmd::Stmt(ref stmt) | Cmd::Explain(ref stmt) => {
|
||||
let program = translate::translate(
|
||||
@@ -1177,14 +1176,16 @@ impl Connection {
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
cmd.into(),
|
||||
mode,
|
||||
input,
|
||||
)?;
|
||||
let stmt = Statement::new(program, self._db.mv_store.clone(), pager);
|
||||
let stmt = Statement::new(program, self._db.mv_store.clone(), pager, mode);
|
||||
Ok(Some(stmt))
|
||||
}
|
||||
Cmd::ExplainQueryPlan(stmt) => {
|
||||
let mut table_ref_counter = TableRefIdCounter::new();
|
||||
|
||||
// TODO: we need OP_Explain
|
||||
match stmt {
|
||||
ast::Stmt::Select(select) => {
|
||||
let mut plan = prepare_select_plan(
|
||||
@@ -1227,35 +1228,19 @@ impl Connection {
|
||||
let input = str::from_utf8(&sql.as_bytes()[..byte_offset_end])
|
||||
.unwrap()
|
||||
.trim();
|
||||
match cmd {
|
||||
Cmd::Explain(stmt) => {
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager,
|
||||
self.clone(),
|
||||
&syms,
|
||||
QueryMode::Explain,
|
||||
input,
|
||||
)?;
|
||||
let _ = std::io::stdout().write_all(program.explain().as_bytes());
|
||||
}
|
||||
Cmd::ExplainQueryPlan(_stmt) => todo!(),
|
||||
Cmd::Stmt(stmt) => {
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
QueryMode::Normal,
|
||||
input,
|
||||
)?;
|
||||
|
||||
Statement::new(program, self._db.mv_store.clone(), pager.clone())
|
||||
.run_ignore_rows()?;
|
||||
}
|
||||
}
|
||||
let mode = QueryMode::new(&cmd);
|
||||
let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;
|
||||
let program = translate::translate(
|
||||
self.schema.borrow().deref(),
|
||||
stmt,
|
||||
pager.clone(),
|
||||
self.clone(),
|
||||
&syms,
|
||||
mode,
|
||||
input,
|
||||
)?;
|
||||
Statement::new(program, self._db.mv_store.clone(), pager.clone(), mode)
|
||||
.run_ignore_rows()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2090,20 +2075,39 @@ pub struct Statement {
|
||||
/// Used to determine whether we need to check for schema changes when
|
||||
/// starting a transaction.
|
||||
accesses_db: bool,
|
||||
|
||||
/// indicates if the statement is a NORMAL/EXPLAIN/EXPLAIN QUERY PLAN
|
||||
query_mode: QueryMode,
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
pub fn new(program: vdbe::Program, mv_store: Option<Arc<MvStore>>, pager: Rc<Pager>) -> Self {
|
||||
pub fn new(
|
||||
program: vdbe::Program,
|
||||
mv_store: Option<Arc<MvStore>>,
|
||||
pager: Rc<Pager>,
|
||||
query_mode: QueryMode,
|
||||
) -> Self {
|
||||
let accesses_db = program.accesses_db;
|
||||
let state = vdbe::ProgramState::new(program.max_registers, program.cursor_ref.len());
|
||||
let state = vdbe::ProgramState::new(
|
||||
match query_mode {
|
||||
QueryMode::Normal => program.max_registers,
|
||||
QueryMode::Explain => EXPLAIN_COLUMNS.len(),
|
||||
QueryMode::ExplainQueryPlan => EXPLAIN_QUERY_PLAN_COLUMNS.len(),
|
||||
},
|
||||
program.cursor_ref.len(),
|
||||
);
|
||||
Self {
|
||||
program,
|
||||
state,
|
||||
mv_store,
|
||||
pager,
|
||||
accesses_db,
|
||||
query_mode,
|
||||
}
|
||||
}
|
||||
pub fn get_query_mode(&self) -> QueryMode {
|
||||
self.query_mode
|
||||
}
|
||||
|
||||
pub fn n_change(&self) -> i64 {
|
||||
self.program.n_change.get()
|
||||
@@ -2119,13 +2123,20 @@ impl Statement {
|
||||
|
||||
pub fn step(&mut self) -> Result<StepResult> {
|
||||
let res = if !self.accesses_db {
|
||||
self.program
|
||||
.step(&mut self.state, self.mv_store.clone(), self.pager.clone())
|
||||
self.program.step(
|
||||
&mut self.state,
|
||||
self.mv_store.clone(),
|
||||
self.pager.clone(),
|
||||
self.query_mode,
|
||||
)
|
||||
} else {
|
||||
const MAX_SCHEMA_RETRY: usize = 50;
|
||||
let mut res =
|
||||
self.program
|
||||
.step(&mut self.state, self.mv_store.clone(), self.pager.clone());
|
||||
let mut res = self.program.step(
|
||||
&mut self.state,
|
||||
self.mv_store.clone(),
|
||||
self.pager.clone(),
|
||||
self.query_mode,
|
||||
);
|
||||
for attempt in 0..MAX_SCHEMA_RETRY {
|
||||
// Only reprepare if we still need to update schema
|
||||
if !matches!(res, Err(LimboError::SchemaUpdated)) {
|
||||
@@ -2133,9 +2144,12 @@ impl Statement {
|
||||
}
|
||||
tracing::debug!("reprepare: attempt={}", attempt);
|
||||
self.reprepare()?;
|
||||
res = self
|
||||
.program
|
||||
.step(&mut self.state, self.mv_store.clone(), self.pager.clone());
|
||||
res = self.program.step(
|
||||
&mut self.state,
|
||||
self.mv_store.clone(),
|
||||
self.pager.clone(),
|
||||
self.query_mode,
|
||||
);
|
||||
}
|
||||
res
|
||||
};
|
||||
@@ -2190,20 +2204,18 @@ impl Statement {
|
||||
let cmd = cmd.expect("Same SQL string should be able to be parsed");
|
||||
|
||||
let syms = conn.syms.borrow();
|
||||
|
||||
match cmd {
|
||||
Cmd::Stmt(stmt) => translate::translate(
|
||||
conn.schema.borrow().deref(),
|
||||
stmt,
|
||||
self.pager.clone(),
|
||||
conn.clone(),
|
||||
&syms,
|
||||
QueryMode::Normal,
|
||||
&self.program.sql,
|
||||
)?,
|
||||
Cmd::Explain(_stmt) => todo!(),
|
||||
Cmd::ExplainQueryPlan(_stmt) => todo!(),
|
||||
}
|
||||
let mode = self.query_mode;
|
||||
debug_assert_eq!(QueryMode::new(&cmd), mode,);
|
||||
let (Cmd::Stmt(stmt) | Cmd::Explain(stmt) | Cmd::ExplainQueryPlan(stmt)) = cmd;
|
||||
translate::translate(
|
||||
conn.schema.borrow().deref(),
|
||||
stmt,
|
||||
self.pager.clone(),
|
||||
conn.clone(),
|
||||
&syms,
|
||||
mode,
|
||||
&self.program.sql,
|
||||
)?
|
||||
};
|
||||
// Save parameters before they are reset
|
||||
let parameters = std::mem::take(&mut self.state.parameters);
|
||||
@@ -2239,14 +2251,24 @@ impl Statement {
|
||||
}
|
||||
|
||||
pub fn num_columns(&self) -> usize {
|
||||
self.program.result_columns.len()
|
||||
match self.query_mode {
|
||||
QueryMode::Normal => self.program.result_columns.len(),
|
||||
QueryMode::Explain => EXPLAIN_COLUMNS.len(),
|
||||
QueryMode::ExplainQueryPlan => EXPLAIN_QUERY_PLAN_COLUMNS.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column_name(&self, idx: usize) -> Cow<str> {
|
||||
let column = &self.program.result_columns.get(idx).expect("No column");
|
||||
match column.name(&self.program.table_references) {
|
||||
Some(name) => Cow::Borrowed(name),
|
||||
None => Cow::Owned(column.expr.to_string()),
|
||||
match self.query_mode {
|
||||
QueryMode::Normal => {
|
||||
let column = &self.program.result_columns.get(idx).expect("No column");
|
||||
match column.name(&self.program.table_references) {
|
||||
Some(name) => Cow::Borrowed(name),
|
||||
None => Cow::Owned(column.expr.to_string()),
|
||||
}
|
||||
}
|
||||
QueryMode::Explain => Cow::Borrowed(EXPLAIN_COLUMNS[idx]),
|
||||
QueryMode::ExplainQueryPlan => Cow::Borrowed(EXPLAIN_QUERY_PLAN_COLUMNS[idx]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2303,10 +2325,6 @@ impl Statement {
|
||||
pub fn row(&self) -> Option<&Row> {
|
||||
self.state.result_row.as_ref()
|
||||
}
|
||||
|
||||
pub fn explain(&self) -> String {
|
||||
self.program.explain()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Row = vdbe::Row;
|
||||
|
||||
@@ -139,13 +139,15 @@ impl CursorType {
|
||||
pub enum QueryMode {
|
||||
Normal,
|
||||
Explain,
|
||||
ExplainQueryPlan,
|
||||
}
|
||||
|
||||
impl From<ast::Cmd> for QueryMode {
|
||||
fn from(stmt: ast::Cmd) -> Self {
|
||||
match stmt {
|
||||
ast::Cmd::ExplainQueryPlan(_) | ast::Cmd::Explain(_) => QueryMode::Explain,
|
||||
_ => QueryMode::Normal,
|
||||
impl QueryMode {
|
||||
pub fn new(cmd: &ast::Cmd) -> Self {
|
||||
match cmd {
|
||||
ast::Cmd::ExplainQueryPlan(_) => QueryMode::ExplainQueryPlan,
|
||||
ast::Cmd::Explain(_) => QueryMode::Explain,
|
||||
ast::Cmd::Stmt(_) => QueryMode::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,7 +173,7 @@ impl ProgramBuilder {
|
||||
constant_spans: Vec::new(),
|
||||
label_to_resolved_offset: Vec::with_capacity(opts.approx_num_labels),
|
||||
seekrowid_emitted_bitmask: 0,
|
||||
comments: if query_mode == QueryMode::Explain {
|
||||
comments: if let QueryMode::Explain | QueryMode::ExplainQueryPlan = query_mode {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -5,13 +5,13 @@ use crate::vdbe::{builder::CursorType, insn::RegisterOrLiteral};
|
||||
use super::{Insn, InsnReference, Program, Value};
|
||||
use crate::function::{Func, ScalarFunc};
|
||||
|
||||
pub fn insn_to_str(
|
||||
pub const EXPLAIN_COLUMNS: [&str; 8] = ["addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment"];
|
||||
pub const EXPLAIN_QUERY_PLAN_COLUMNS: [&str; 4] = ["id", "parent", "notused", "detail"];
|
||||
|
||||
pub fn insn_to_row(
|
||||
program: &Program,
|
||||
addr: InsnReference,
|
||||
insn: &Insn,
|
||||
indent: String,
|
||||
manual_comment: Option<&'static str>,
|
||||
) -> String {
|
||||
) -> (&'static str, i32, i32, i32, Value, u16, String) {
|
||||
let get_table_or_index_name = |cursor_id: usize| {
|
||||
let cursor_type = &program.cursor_ref[cursor_id].1;
|
||||
match cursor_type {
|
||||
@@ -23,8 +23,7 @@ pub fn insn_to_str(
|
||||
CursorType::Sorter => "sorter",
|
||||
}
|
||||
};
|
||||
let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, Value, u16, String) =
|
||||
match insn {
|
||||
match insn {
|
||||
Insn::Init { target_pc } => (
|
||||
"Init",
|
||||
0,
|
||||
@@ -1724,7 +1723,34 @@ pub fn insn_to_str(
|
||||
0,
|
||||
format!("if (r[{}] < 0) goto {}", reg, target_pc.as_debug_int()),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insn_to_row_with_comment(
|
||||
program: &Program,
|
||||
insn: &Insn,
|
||||
manual_comment: Option<&str>,
|
||||
) -> (&'static str, i32, i32, i32, Value, u16, String) {
|
||||
let (opcode, p1, p2, p3, p4, p5, comment) = insn_to_row(program, insn);
|
||||
(
|
||||
opcode,
|
||||
p1,
|
||||
p2,
|
||||
p3,
|
||||
p4,
|
||||
p5,
|
||||
manual_comment.map_or(comment.to_string(), |mc| format!("{comment}; {mc}")),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insn_to_str(
|
||||
program: &Program,
|
||||
addr: InsnReference,
|
||||
insn: &Insn,
|
||||
indent: String,
|
||||
manual_comment: Option<&str>,
|
||||
) -> String {
|
||||
let (opcode, p1, p2, p3, p4, p5, comment) = insn_to_row(program, insn);
|
||||
format!(
|
||||
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",
|
||||
addr,
|
||||
|
||||
182
core/vdbe/mod.rs
182
core/vdbe/mod.rs
@@ -53,12 +53,13 @@ use crate::{
|
||||
#[cfg(feature = "json")]
|
||||
use crate::json::JsonCacheCell;
|
||||
use crate::{Connection, MvStore, Result, TransactionState};
|
||||
use builder::CursorKey;
|
||||
use builder::{CursorKey, QueryMode};
|
||||
use execute::{
|
||||
InsnFunction, InsnFunctionStepResult, OpIdxDeleteState, OpIntegrityCheckState,
|
||||
OpOpenEphemeralState,
|
||||
};
|
||||
|
||||
use explain::{insn_to_row_with_comment, EXPLAIN_COLUMNS, EXPLAIN_QUERY_PLAN_COLUMNS};
|
||||
use regex::Regex;
|
||||
use std::{cell::Cell, collections::HashMap, num::NonZero, rc::Rc, sync::Arc};
|
||||
use tracing::{instrument, Level};
|
||||
@@ -465,12 +466,92 @@ impl Program {
|
||||
self.connection.get_pager_from_database_index(idx)
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = Level::DEBUG)]
|
||||
pub fn step(
|
||||
&self,
|
||||
state: &mut ProgramState,
|
||||
mv_store: Option<Arc<MvStore>>,
|
||||
pager: Rc<Pager>,
|
||||
query_mode: QueryMode,
|
||||
) -> Result<StepResult> {
|
||||
match query_mode {
|
||||
QueryMode::Normal => self.normal_step(state, mv_store, pager),
|
||||
QueryMode::Explain => self.explain_step(state, mv_store, pager),
|
||||
QueryMode::ExplainQueryPlan => self.explain_query_plan_step(state, mv_store, pager),
|
||||
}
|
||||
}
|
||||
|
||||
fn explain_step(
|
||||
&self,
|
||||
state: &mut ProgramState,
|
||||
_mv_store: Option<Arc<MvStore>>,
|
||||
pager: Rc<Pager>,
|
||||
) -> Result<StepResult> {
|
||||
debug_assert!(state.column_count() == EXPLAIN_COLUMNS.len());
|
||||
if self.connection.closed.get() {
|
||||
// Connection is closed for whatever reason, rollback the transaction.
|
||||
let state = self.connection.transaction_state.get();
|
||||
if let TransactionState::Write { .. } = state {
|
||||
pager.io.block(|| pager.end_tx(true, &self.connection))?;
|
||||
}
|
||||
return Err(LimboError::InternalError("Connection closed".to_string()));
|
||||
}
|
||||
|
||||
if state.is_interrupted() {
|
||||
return Ok(StepResult::Interrupt);
|
||||
}
|
||||
|
||||
// FIXME: do we need this?
|
||||
state.metrics.vm_steps = state.metrics.vm_steps.saturating_add(1);
|
||||
|
||||
if state.pc as usize >= self.insns.len() {
|
||||
return Ok(StepResult::Done);
|
||||
}
|
||||
|
||||
let (current_insn, _) = &self.insns[state.pc as usize];
|
||||
let (opcode, p1, p2, p3, p4, p5, comment) = insn_to_row_with_comment(
|
||||
self,
|
||||
current_insn,
|
||||
self.comments.as_ref().and_then(|comments| {
|
||||
comments
|
||||
.iter()
|
||||
.find(|(offset, _)| *offset == state.pc)
|
||||
.map(|(_, comment)| comment)
|
||||
.copied()
|
||||
}),
|
||||
);
|
||||
|
||||
state.registers[0] = Register::Value(Value::Integer(state.pc as i64));
|
||||
state.registers[1] = Register::Value(Value::from_text(opcode));
|
||||
state.registers[2] = Register::Value(Value::Integer(p1 as i64));
|
||||
state.registers[3] = Register::Value(Value::Integer(p2 as i64));
|
||||
state.registers[4] = Register::Value(Value::Integer(p3 as i64));
|
||||
state.registers[5] = Register::Value(p4);
|
||||
state.registers[6] = Register::Value(Value::Integer(p5 as i64));
|
||||
state.registers[7] = Register::Value(Value::from_text(&comment));
|
||||
state.result_row = Some(Row {
|
||||
values: &state.registers[0] as *const Register,
|
||||
count: EXPLAIN_COLUMNS.len(),
|
||||
});
|
||||
state.pc += 1;
|
||||
Ok(StepResult::Row)
|
||||
}
|
||||
|
||||
fn explain_query_plan_step(
|
||||
&self,
|
||||
state: &mut ProgramState,
|
||||
_mv_store: Option<Arc<MvStore>>,
|
||||
_pager: Rc<Pager>,
|
||||
) -> Result<StepResult> {
|
||||
debug_assert!(state.column_count() == EXPLAIN_QUERY_PLAN_COLUMNS.len());
|
||||
todo!("we need OP_Explain to be implemented first")
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = Level::DEBUG)]
|
||||
fn normal_step(
|
||||
&self,
|
||||
state: &mut ProgramState,
|
||||
mv_store: Option<Arc<MvStore>>,
|
||||
pager: Rc<Pager>,
|
||||
) -> Result<StepResult> {
|
||||
let enable_tracing = tracing::enabled!(tracing::Level::TRACE);
|
||||
loop {
|
||||
@@ -787,27 +868,6 @@ impl Program {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn explain(&self) -> String {
|
||||
let mut buff = String::with_capacity(1024);
|
||||
buff.push_str("addr opcode p1 p2 p3 p4 p5 comment\n");
|
||||
buff.push_str("---- ----------------- ---- ---- ---- ------------- -- -------\n");
|
||||
let indent = " ";
|
||||
let indent_counts = get_indent_counts(&self.insns);
|
||||
for (addr, (insn, _)) in self.insns.iter().enumerate() {
|
||||
let indent_count = indent_counts[addr];
|
||||
print_insn(
|
||||
self,
|
||||
addr as InsnReference,
|
||||
insn,
|
||||
indent.repeat(indent_count),
|
||||
&mut buff,
|
||||
);
|
||||
buff.push('\n');
|
||||
}
|
||||
buff
|
||||
}
|
||||
}
|
||||
|
||||
fn make_record(registers: &[Register], start_reg: &usize, count: &usize) -> ImmutableRecord {
|
||||
@@ -852,82 +912,6 @@ fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {
|
||||
);
|
||||
}
|
||||
|
||||
fn print_insn(program: &Program, addr: InsnReference, insn: &Insn, indent: String, w: &mut String) {
|
||||
let s = explain::insn_to_str(
|
||||
program,
|
||||
addr,
|
||||
insn,
|
||||
indent,
|
||||
program.comments.as_ref().and_then(|comments| {
|
||||
comments
|
||||
.iter()
|
||||
.find(|(offset, _)| *offset == addr)
|
||||
.map(|(_, comment)| comment)
|
||||
.copied()
|
||||
}),
|
||||
);
|
||||
w.push_str(&s);
|
||||
}
|
||||
|
||||
// The indenting rules are(from SQLite):
|
||||
//
|
||||
// * For each "Next", "Prev", "VNext" or "VPrev" instruction, increase the ident number for
|
||||
// all opcodes that occur between the p2 jump destination and the opcode itself.
|
||||
//
|
||||
// * Do the previous for "Return" instructions for when P2 is positive.
|
||||
//
|
||||
// * For each "Goto", if the jump destination is earlier in the program and ends on one of:
|
||||
// Yield SeekGt SeekLt RowSetRead Rewind
|
||||
// or if the P1 parameter is one instead of zero, then increase the indent number for all
|
||||
// opcodes between the earlier instruction and "Goto"
|
||||
fn get_indent_counts(insns: &[(Insn, InsnFunction)]) -> Vec<usize> {
|
||||
let mut indents = vec![0; insns.len()];
|
||||
|
||||
for (i, (insn, _)) in insns.iter().enumerate() {
|
||||
let mut start = 0;
|
||||
let mut end = 0;
|
||||
match insn {
|
||||
Insn::Next { pc_if_next, .. } | Insn::VNext { pc_if_next, .. } => {
|
||||
let dest = pc_if_next.as_debug_int() as usize;
|
||||
if dest < i {
|
||||
start = dest;
|
||||
end = i;
|
||||
}
|
||||
}
|
||||
Insn::Prev { pc_if_prev, .. } => {
|
||||
let dest = pc_if_prev.as_debug_int() as usize;
|
||||
if dest < i {
|
||||
start = dest;
|
||||
end = i;
|
||||
}
|
||||
}
|
||||
|
||||
Insn::Goto { target_pc } => {
|
||||
let dest = target_pc.as_debug_int() as usize;
|
||||
if dest < i
|
||||
&& matches!(
|
||||
insns.get(dest).map(|(insn, _)| insn),
|
||||
Some(Insn::Yield { .. })
|
||||
| Some(Insn::SeekGT { .. })
|
||||
| Some(Insn::SeekLT { .. })
|
||||
| Some(Insn::Rewind { .. })
|
||||
)
|
||||
{
|
||||
start = dest;
|
||||
end = i;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
for indent in indents.iter_mut().take(end).skip(start) {
|
||||
*indent += 1;
|
||||
}
|
||||
}
|
||||
|
||||
indents
|
||||
}
|
||||
|
||||
pub trait FromValueRow<'a> {
|
||||
fn from_value(value: &'a Value) -> Result<Self>
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user