From d97ba9e409c7902e9a506a7001c100854ab7dc7e Mon Sep 17 00:00:00 2001 From: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Date: Sat, 7 Mar 2026 08:56:23 -0500 Subject: [PATCH] feat(db.sqlite): add Row.names, get_string/get_int, and improve exec_map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `names []string` to `Row` so callers can access column values by name rather than by hardcoded index. Column names are captured once after sqlite3_prepare_v2 (before stepping) and the same slice is assigned to every row, avoiding per-row allocation overhead. `names` is placed after `vals` to preserve positional struct literal compatibility — `Row{['a', 'b']}` continues to populate `vals`. Add `get_string(col_name string) string` and `get_int(col_name string) int` accessor methods on `Row`. Both return zero values gracefully when the column name is not found, preserving compatibility with manually constructed Row instances that have no names. Improve exec_map to capture column names once before the row loop instead of calling sqlite3_column_name on every column of every row. For a result set of N rows and C columns this reduces sqlite3_column_name calls from N*C to C. Closes #26701 Co-Authored-By: Claude Sonnet 4.6 --- vlib/db/sqlite/sqlite.c.v | 57 +++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/vlib/db/sqlite/sqlite.c.v b/vlib/db/sqlite/sqlite.c.v index f1e03c3844e01b..eca910fc6cc1ef 100644 --- a/vlib/db/sqlite/sqlite.c.v +++ b/vlib/db/sqlite/sqlite.c.v @@ -88,7 +88,27 @@ pub fn (db &DB) str() string { pub struct Row { pub mut: - vals []string + vals []string + names []string +} + +// get_string returns the value for the given column name, or '' if the column is not found +// or if the corresponding value index is out of range. +pub fn (r &Row) get_string(col_name string) string { + for i, name in r.names { + if name == col_name { + if i < r.vals.len { + return r.vals[i] + } + return '' + } + } + return '' +} + +// get_int returns the integer value for the given column name, or 0 if the column is not found. +pub fn (r &Row) get_int(col_name string) int { + return r.get_string(col_name).int() } pub type Params = []string | [][]string @@ -246,6 +266,11 @@ pub fn (db &DB) exec_map(query string) ![]map[string]string { C.sqlite3_finalize(stmt) } nr_cols := C.sqlite3_column_count(stmt) + mut col_names := []string{cap: nr_cols} + for i in 0 .. nr_cols { + col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) } + col_names << if col_char != unsafe { nil } { unsafe { tos_clone(col_char) } } else { '' } + } mut res := 0 mut rows := []map[string]string{} for { @@ -256,13 +281,7 @@ pub fn (db &DB) exec_map(query string) ![]map[string]string { mut row := map[string]string{} for i in 0 .. nr_cols { val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) } - col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) } - col := unsafe { tos_clone(col_char) } - if val == &u8(unsafe { nil }) { - row[col] = '' - } else { - row[col] = unsafe { tos_clone(val) } - } + row[col_names[i]] = if val != unsafe { nil } { unsafe { tos_clone(val) } } else { '' } } rows << row } @@ -285,6 +304,11 @@ pub fn (db &DB) exec(query string) ![]Row { C.sqlite3_finalize(stmt) } nr_cols := C.sqlite3_column_count(stmt) + mut col_names := []string{cap: nr_cols} + for i in 0 .. nr_cols { + col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) } + col_names << if col_char != unsafe { nil } { unsafe { tos_clone(col_char) } } else { '' } + } mut res := 0 mut rows := []Row{} for { @@ -292,7 +316,9 @@ pub fn (db &DB) exec(query string) ![]Row { if res != sqlite_row { break } - mut row := Row{} + mut row := Row{ + names: col_names + } for i in 0 .. nr_cols { val := unsafe { &u8(C.sqlite3_column_text(stmt, i)) } if val == &u8(unsafe { nil }) { @@ -372,6 +398,11 @@ pub fn (db &DB) exec_param_many(query string, params Params) ![]Row { mut rows := []Row{} nr_cols := C.sqlite3_column_count(stmt) + mut col_names := []string{cap: nr_cols} + for i in 0 .. nr_cols { + col_char := unsafe { &u8(C.sqlite3_column_name(stmt, i)) } + col_names << if col_char != unsafe { nil } { unsafe { tos_clone(col_char) } } else { '' } + } if params is []string { for i, param in params { @@ -381,7 +412,9 @@ pub fn (db &DB) exec_param_many(query string, params Params) ![]Row { } } for { - mut row := Row{} + mut row := Row{ + names: col_names + } code = C.sqlite3_step(stmt) if is_error(code) { return db.error_message(code, query) @@ -402,7 +435,9 @@ pub fn (db &DB) exec_param_many(query string, params Params) ![]Row { } else if params is [][]string { // Rows to process for params_row in params { - mut row := Row{} + mut row := Row{ + names: col_names + } // Param values to bind for i, param in params_row { code = C.sqlite3_bind_text(stmt, i + 1, voidptr(param.str), param.len,