Merge 'emit proper column information for explain prepared statements' from Nikita Sivukhin

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3612
This commit is contained in:
Pekka Enberg
2025-10-07 13:44:17 +03:00
committed by GitHub
3 changed files with 127 additions and 0 deletions

View File

@@ -1,6 +1,97 @@
import { expect, test } from 'vitest'
import { connect, Database } from './promise-default.js'
test('explain', async () => {
const db = await connect(":memory:");
const stmt = db.prepare("EXPLAIN SELECT 1");
expect(stmt.columns()).toEqual([
{
"name": "addr",
"type": "INTEGER",
},
{
"name": "opcode",
"type": "TEXT",
},
{
"name": "p1",
"type": "INTEGER",
},
{
"name": "p2",
"type": "INTEGER",
},
{
"name": "p3",
"type": "INTEGER",
},
{
"name": "p4",
"type": "INTEGER",
},
{
"name": "p5",
"type": "INTEGER",
},
{
"name": "comment",
"type": "TEXT",
},
].map(x => ({ ...x, column: null, database: null, table: null })));
expect(await stmt.all()).toEqual([
{
"addr": 0,
"comment": "Start at 3",
"opcode": "Init",
"p1": 0,
"p2": 3,
"p3": 0,
"p4": "",
"p5": 0,
},
{
"addr": 1,
"comment": "output=r[1]",
"opcode": "ResultRow",
"p1": 1,
"p2": 1,
"p3": 0,
"p4": "",
"p5": 0,
},
{
"addr": 2,
"comment": "",
"opcode": "Halt",
"p1": 0,
"p2": 0,
"p3": 0,
"p4": "",
"p5": 0,
},
{
"addr": 3,
"comment": "r[1]=1",
"opcode": "Integer",
"p1": 1,
"p2": 1,
"p3": 0,
"p4": "",
"p5": 0,
},
{
"addr": 4,
"comment": "",
"opcode": "Goto",
"p1": 0,
"p2": 1,
"p3": 0,
"p4": "",
"p5": 0,
},
]);
})
test('in-memory db', async () => {
const db = await connect(":memory:");
await db.exec("CREATE TABLE t(x)");
@@ -10,6 +101,7 @@ test('in-memory db', async () => {
expect(rows).toEqual([{ x: 1 }, { x: 3 }]);
})
test('implicit connect', async () => {
const db = new Database(':memory:');
const defer = db.prepare("SELECT * FROM t");

View File

@@ -46,6 +46,7 @@ use crate::translate::pragma::TURSO_CDC_DEFAULT_TABLE_NAME;
use crate::types::{WalFrameInfo, WalState};
#[cfg(feature = "fs")]
use crate::util::{OpenMode, OpenOptions};
use crate::vdbe::explain::{EXPLAIN_COLUMNS_TYPE, EXPLAIN_QUERY_PLAN_COLUMNS_TYPE};
use crate::vdbe::metrics::ConnectionMetrics;
use crate::vtab::VirtualTable;
use crate::{incremental::view::AllViewsTxState, translate::emitter::TransactionMode};
@@ -2667,6 +2668,17 @@ impl Statement {
}
pub fn get_column_name(&self, idx: usize) -> Cow<'_, str> {
if self.query_mode == QueryMode::Explain {
return Cow::Owned(EXPLAIN_COLUMNS.get(idx).expect("No column").to_string());
}
if self.query_mode == QueryMode::ExplainQueryPlan {
return Cow::Owned(
EXPLAIN_QUERY_PLAN_COLUMNS
.get(idx)
.expect("No column")
.to_string(),
);
}
match self.query_mode {
QueryMode::Normal => {
let column = &self.program.result_columns.get(idx).expect("No column");
@@ -2685,6 +2697,9 @@ impl Statement {
}
pub fn get_column_table_name(&self, idx: usize) -> Option<Cow<'_, str>> {
if self.query_mode == QueryMode::Explain || self.query_mode == QueryMode::ExplainQueryPlan {
return None;
}
let column = &self.program.result_columns.get(idx).expect("No column");
match &column.expr {
turso_parser::ast::Expr::Column { table, .. } => self
@@ -2697,6 +2712,22 @@ impl Statement {
}
pub fn get_column_type(&self, idx: usize) -> Option<String> {
if self.query_mode == QueryMode::Explain {
return Some(
EXPLAIN_COLUMNS_TYPE
.get(idx)
.expect("No column")
.to_string(),
);
}
if self.query_mode == QueryMode::ExplainQueryPlan {
return Some(
EXPLAIN_QUERY_PLAN_COLUMNS_TYPE
.get(idx)
.expect("No column")
.to_string(),
);
}
let column = &self.program.result_columns.get(idx).expect("No column");
match &column.expr {
turso_parser::ast::Expr::Column {

View File

@@ -6,7 +6,11 @@ use super::{Insn, InsnReference, Program, Value};
use crate::function::{Func, ScalarFunc};
pub const EXPLAIN_COLUMNS: [&str; 8] = ["addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment"];
pub const EXPLAIN_COLUMNS_TYPE: [&str; 8] = [
"INTEGER", "TEXT", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER", "TEXT",
];
pub const EXPLAIN_QUERY_PLAN_COLUMNS: [&str; 4] = ["id", "parent", "notused", "detail"];
pub const EXPLAIN_QUERY_PLAN_COLUMNS_TYPE: [&str; 4] = ["INTEGER", "INTEGER", "INTEGER", "TEXT"];
pub fn insn_to_row(
program: &Program,