From e8b898af6341bdb6e2838c505711caeba384e99b Mon Sep 17 00:00:00 2001 From: Richard Wheeler Date: Mon, 30 Mar 2026 16:59:04 -0400 Subject: [PATCH] db.sqlite: add tables, columns, schema, db_size methods (#26791) * db.sqlite: add tables, columns, schema, and db_size convenience methods Add four methods to sqlite.DB for common introspection queries: - tables() returns all user table names - columns(table) returns column names for a table - schema(table) returns CREATE statements - db_size() returns database file size via page_count * page_size Co-Authored-By: Claude Opus 4.6 * db.sqlite: escape table names in columns() and schema() Quote table names with double-quote escaping in columns() PRAGMA and single-quote escaping in schema() filter to handle table names containing spaces, punctuation, or quotes. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Richard Wheeler Co-authored-by: Claude Opus 4.6 --- vlib/db/sqlite/sqlite.c.v | 37 ++++++++++++++++++++++++++++++++ vlib/db/sqlite/sqlite_test.v | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index 0946a2da8..5d792c577 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -582,6 +582,43 @@ pub fn (mut db DB) release_savepoint(savepoint string) ! { db.exec('RELEASE SAVEPOINT ${savepoint};')! } +// tables returns the names of all user tables in the database. +pub fn (db &DB) tables() ![]string { + rows := db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")! + return rows.map(it.vals[0]) +} + +// columns returns the column names for the given table. +pub fn (db &DB) columns(table string) ![]string { + escaped := table.replace('"', '""') + rows := db.exec('PRAGMA table_info("${escaped}")')! + return rows.map(it.vals[1]) +} + +// schema returns the CREATE statement(s) for the given table, or for all +// objects if table is empty. +pub fn (db &DB) schema(table string) !string { + filter := if table != '' { + escaped := table.replace("'", "''") + "AND name='${escaped}'" + } else { + '' + } + rows := db.exec("SELECT sql FROM sqlite_master WHERE type IN ('table','index','view','trigger') ${filter} AND sql IS NOT NULL ORDER BY type, name")! + return rows.map(it.vals[0]).join('\n\n') +} + +// db_size returns the database file size in bytes, computed from page_count +// and page_size. +pub fn (db &DB) db_size() !i64 { + pc := db.exec('PRAGMA page_count')! + ps := db.exec('PRAGMA page_size')! + if pc.len == 0 || ps.len == 0 { + return 0 + } + return pc[0].vals[0].i64() * ps[0].vals[0].i64() +} + // reset returns the connection to initial state for reuse pub fn (mut db DB) reset() ! { } diff --git a/vlib/db/sqlite/sqlite_test.v b/vlib/db/sqlite/sqlite_test.v index 22fdb9b2d..5e1de389c 100644 --- a/vlib/db/sqlite/sqlite_test.v +++ b/vlib/db/sqlite/sqlite_test.v @@ -1,6 +1,7 @@ // vtest build: present_sqlite3? import db.sqlite import orm +import os type Connection = sqlite.DB @@ -148,6 +149,46 @@ fn test_exec_param_many2() { db.close()! } +fn test_tables() { + mut db := sqlite.connect(':memory:') or { panic(err) } + db.exec('create table alpha (id integer)')! + db.exec('create table beta (id integer)')! + tbl := db.tables()! + assert tbl == ['alpha', 'beta'] + db.close()! +} + +fn test_columns() { + mut db := sqlite.connect(':memory:') or { panic(err) } + db.exec('create table items (id integer primary key, name text, price real)')! + cols := db.columns('items')! + assert cols == ['id', 'name', 'price'] + db.close()! +} + +fn test_schema() { + mut db := sqlite.connect(':memory:') or { panic(err) } + db.exec('create table things (id integer primary key, label text)')! + s := db.schema('things')! + assert s.contains('CREATE TABLE things') + // empty table name returns all objects + all := db.schema('')! + assert all.contains('CREATE TABLE things') + db.close()! +} + +fn test_db_size() { + tmp := os.join_path(os.temp_dir(), 'test_db_size.db') + defer { + os.rm(tmp) or {} + } + mut db := sqlite.connect(tmp) or { panic(err) } + db.exec('create table t (id integer)')! + sz := db.db_size()! + assert sz > 0 + db.close()! +} + fn test_orm_transaction_interface() { mut db := sqlite.connect(':memory:') or { panic(err) } defer { -- 2.39.5