implement pragma table_info

Both () and = variants covered. It is important to make sure that
the transaction is a read transaction, so we cannot hide all that logic
inside update_pragma, and have to make our decision before that.
This commit is contained in:
Glauber Costa
2025-01-30 15:26:02 -05:00
parent 249a8cf8d2
commit 016b815b59
4 changed files with 154 additions and 13 deletions

View File

@@ -151,7 +151,7 @@ The current status of Limbo is:
| PRAGMA soft_heap_limit | No | |
| PRAGMA stats | No | Used for testing in SQLite |
| PRAGMA synchronous | No | |
| PRAGMA table_info | No | |
| PRAGMA table_info | Yes | |
| PRAGMA table_list | No | |
| PRAGMA table_xinfo | No | |
| PRAGMA temp_store | No | |

View File

@@ -27,7 +27,7 @@ use crate::storage::pager::Pager;
use crate::storage::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
use crate::storage::wal::CheckpointMode;
use crate::translate::delete::translate_delete;
use crate::util::PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX;
use crate::util::{normalize_ident, PRIMARY_KEY_AUTOMATIC_INDEX_NAME_PREFIX};
use crate::vdbe::builder::CursorType;
use crate::vdbe::{builder::ProgramBuilder, insn::Insn, Program};
use crate::{bail_parse_error, Connection, LimboError, Result, SymbolTable};
@@ -90,7 +90,14 @@ pub fn translate(
ast::Stmt::DropTrigger { .. } => bail_parse_error!("DROP TRIGGER not supported yet"),
ast::Stmt::DropView { .. } => bail_parse_error!("DROP VIEW not supported yet"),
ast::Stmt::Pragma(name, body) => {
translate_pragma(&mut program, &name, body, database_header.clone(), pager)?;
translate_pragma(
&mut program,
&schema,
&name,
body,
database_header.clone(),
pager,
)?;
}
ast::Stmt::Reindex { .. } => bail_parse_error!("REINDEX not supported yet"),
ast::Stmt::Release(_) => bail_parse_error!("RELEASE not supported yet"),
@@ -526,6 +533,7 @@ enum PrimaryKeyDefinitionType<'a> {
fn translate_pragma(
program: &mut ProgramBuilder,
schema: &Schema,
name: &ast::QualifiedName,
body: Option<ast::PragmaBody>,
database_header: Rc<RefCell<DatabaseHeader>>,
@@ -545,15 +553,44 @@ fn translate_pragma(
match body {
None => {
query_pragma(pragma, database_header.clone(), program)?;
}
Some(ast::PragmaBody::Equals(value)) => {
write = true;
update_pragma(pragma, value, database_header.clone(), pager, program)?;
}
Some(ast::PragmaBody::Call(_)) => {
todo!()
query_pragma(pragma, schema, None, database_header.clone(), program)?;
}
Some(ast::PragmaBody::Equals(value)) => match pragma {
PragmaName::TableInfo => {
query_pragma(
pragma,
schema,
Some(value),
database_header.clone(),
program,
)?;
}
_ => {
write = true;
update_pragma(
pragma,
schema,
value,
database_header.clone(),
pager,
program,
)?;
}
},
Some(ast::PragmaBody::Call(value)) => match pragma {
PragmaName::TableInfo => {
query_pragma(
pragma,
schema,
Some(value),
database_header.clone(),
program,
)?;
}
_ => {
todo!()
}
},
};
program.emit_insn(Insn::Halt {
err_code: 0,
@@ -571,6 +608,7 @@ fn translate_pragma(
fn update_pragma(
pragma: PragmaName,
schema: &Schema,
value: ast::Expr,
header: Rc<RefCell<DatabaseHeader>>,
pager: Rc<Pager>,
@@ -594,18 +632,26 @@ fn update_pragma(
Ok(())
}
PragmaName::JournalMode => {
query_pragma(PragmaName::JournalMode, header, program)?;
query_pragma(PragmaName::JournalMode, schema, None, header, program)?;
Ok(())
}
PragmaName::WalCheckpoint => {
query_pragma(PragmaName::WalCheckpoint, header, program)?;
query_pragma(PragmaName::WalCheckpoint, schema, None, header, program)?;
Ok(())
}
PragmaName::TableInfo => {
// because we need control over the write parameter for the transaction,
// this should be unreachable. We have to force-call query_pragma before
// getting here
unreachable!();
}
}
}
fn query_pragma(
pragma: PragmaName,
schema: &Schema,
value: Option<ast::Expr>,
database_header: Rc<RefCell<DatabaseHeader>>,
program: &mut ProgramBuilder,
) -> Result<()> {
@@ -646,6 +692,76 @@ fn query_pragma(
count: 3,
});
}
PragmaName::TableInfo => {
let table = match value {
Some(ast::Expr::Name(name)) => {
let tbl = normalize_ident(&name.0);
schema.get_table(&tbl)
}
_ => None,
};
let base_reg = register;
program.alloc_register();
program.alloc_register();
program.alloc_register();
program.alloc_register();
program.alloc_register();
if let Some(table) = table {
for (i, column) in table.columns.iter().enumerate() {
// cid
program.emit_insn(Insn::Integer {
value: i as i64,
dest: base_reg,
});
// name
program.emit_insn(Insn::String8 {
value: column.name.clone(),
dest: base_reg + 1,
});
// type
program.emit_insn(Insn::String8 {
value: column.ty_str.clone(),
dest: base_reg + 2,
});
// notnull
program.emit_insn(Insn::Integer {
value: if column.notnull { 1 } else { 0 },
dest: base_reg + 3,
});
// dflt_value
match &column.default {
None => {
program.emit_insn(Insn::Null {
dest: base_reg + 4,
dest_end: Some(base_reg + 5),
});
}
Some(expr) => {
program.emit_insn(Insn::String8 {
value: expr.to_string(),
dest: base_reg + 4,
});
}
}
// pk
program.emit_insn(Insn::Integer {
value: if column.primary_key { 1 } else { 0 },
dest: base_reg + 5,
});
program.emit_insn(Insn::ResultRow {
start_reg: base_reg,
count: 6,
});
}
}
}
}
Ok(())

View File

@@ -10,3 +10,25 @@ do_execsql_test pragma-cache-size {
do_execsql_test pragma-update-journal-mode-wal {
PRAGMA journal_mode=WAL
} {wal}
do_execsql_test pragma-table-info-equal-syntax {
PRAGMA table_info=sqlite_schema
} {0|type|TEXT|0||0
1|name|TEXT|0||0
2|tbl_name|TEXT|0||0
3|rootpage|INT|0||0
4|sql|TEXT|0||0
}
do_execsql_test pragma-table-info-call-syntax {
PRAGMA table_info(sqlite_schema)
} {0|type|TEXT|0||0
1|name|TEXT|0||0
2|tbl_name|TEXT|0||0
3|rootpage|INT|0||0
4|sql|TEXT|0||0
}
do_execsql_test pragma-table-info-invalid-table {
PRAGMA table_info=pekka
} {}

View File

@@ -1593,6 +1593,8 @@ pub enum PragmaName {
JournalMode,
/// trigger a checkpoint to run on database(s) if WAL is enabled
WalCheckpoint,
/// returns information about the columns of a table
TableInfo,
}
impl FromStr for PragmaName {
@@ -1603,6 +1605,7 @@ impl FromStr for PragmaName {
"cache_size" => Ok(PragmaName::CacheSize),
"wal_checkpoint" => Ok(PragmaName::WalCheckpoint),
"journal_mode" => Ok(PragmaName::JournalMode),
"table_info" => Ok(PragmaName::TableInfo),
_ => Err(()),
}
}