diff --git a/packages/bun-types/sqlite.d.ts b/packages/bun-types/sqlite.d.ts index c894084b8c..94cbca0b95 100644 --- a/packages/bun-types/sqlite.d.ts +++ b/packages/bun-types/sqlite.d.ts @@ -767,36 +767,36 @@ declare module "bun:sqlite" { /** * The actual SQLite column types from the first row of the result set. * Useful for expressions and computed columns, which are not covered by `declaredTypes` - * + * * Returns an array of SQLite type constants as uppercase strings: * - `"INTEGER"` for integer values - * - `"FLOAT"` for floating-point values + * - `"FLOAT"` for floating-point values * - `"TEXT"` for text values * - `"BLOB"` for binary data * - `"NULL"` for null values * - `null` for unknown/unsupported types - * + * * **Requirements:** * - Only available for read-only statements (SELECT queries) * - For non-read-only statements, throws an error - * + * * **Behavior:** * - Uses `sqlite3_column_type()` to get actual data types from the first row * - Returns `null` for columns with unknown SQLite type constants - * + * * @example * ```ts * const stmt = db.prepare("SELECT id, name, age FROM users WHERE id = 1"); - * + * * console.log(stmt.columnTypes); * // => ["INTEGER", "TEXT", "INTEGER"] - * + * * // For expressions: * const exprStmt = db.prepare("SELECT length('bun') AS str_length"); * console.log(exprStmt.columnTypes); * // => ["INTEGER"] * ``` - * + * * @throws Error if statement is not read-only (INSERT, UPDATE, DELETE, etc.) * @since Bun v1.2.13 */ @@ -804,19 +804,19 @@ declare module "bun:sqlite" { /** * The declared column types from the table schema. - * + * * Returns an array of declared type strings from `sqlite3_column_decltype()`: * - Raw type strings as declared in the CREATE TABLE statement * - `null` for columns without declared types (e.g., expressions, computed columns) - * + * * **Requirements:** * - Statement must be executed at least once before accessing this property * - Available for both read-only and read-write statements - * + * * **Behavior:** * - Uses `sqlite3_column_decltype()` to get schema-declared types * - Returns the exact type string from the table definition - * + * * @example * ```ts * // For table columns: @@ -824,14 +824,14 @@ declare module "bun:sqlite" { * stmt.get(); * console.log(stmt.declaredTypes); * // => ["INTEGER", "TEXT", "REAL"] - * + * * // For expressions (no declared types): * const exprStmt = db.prepare("SELECT length('bun') AS str_length"); * exprStmt.get(); * console.log(exprStmt.declaredTypes); * // => [null] * ``` - * + * * @throws Error if statement hasn't been executed * @since Bun v1.2.13 */ @@ -913,10 +913,10 @@ declare module "bun:sqlite" { * Native object representing the underlying `sqlite3_stmt` * * This is left untyped because the ABI of the native bindings may change at any time. - * + * * For stable, typed access to statement metadata, use the typed properties on the Statement class: * - {@link columnNames} for column names - * - {@link paramsCount} for parameter count + * - {@link paramsCount} for parameter count * - {@link columnTypes} for actual data types from the first row * - {@link declaredTypes} for schema-declared column types */ diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp index 33c98eb9dc..7c580590ef 100644 --- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp +++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp @@ -2375,22 +2375,22 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnTypes, (JSGlobalObject * lexical CHECK_PREPARED int count = sqlite3_column_count(castedThis->stmt); - + // We need to reset and step the statement to get fresh types, // but only do this for read-only statements to avoid side effects bool isReadOnly = sqlite3_stmt_readonly(castedThis->stmt) != 0; - if (! isReadOnly) { - // For non-read-only statements, throw an error since column types don't make sense - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "columnTypes is not available for non-read-only statements (INSERT, UPDATE, DELETE, etc.)"_s)); - return { }; + if (!isReadOnly) { + // For non-read-only statements, throw an error since column types don't make sense + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "columnTypes is not available for non-read-only statements (INSERT, UPDATE, DELETE, etc.)"_s)); + return {}; } // Reset the statement (safe for read-only statements) int resetStatus = sqlite3_reset(castedThis->stmt); if (resetStatus != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); - return { }; + return {}; } MarkedArgumentBuffer args; @@ -2439,7 +2439,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnTypes, (JSGlobalObject * lexical // If there was an error stepping, throw it throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(castedThis->stmt); - return { }; + return {}; } // Reset the statement back to its original state @@ -2462,7 +2462,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnDeclaredTypes, (JSGlobalObject * // Ensure the statement has been executed at least once if (!castedThis->hasExecuted) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Statement must be executed before accessing declaredTypes"_s)); - return { }; + return {}; } int count = sqlite3_column_count(castedThis->stmt); @@ -2472,7 +2472,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnDeclaredTypes, (JSGlobalObject * for (int i = 0; i < count; i++) { const char* declType = sqlite3_column_decltype(castedThis->stmt, i); JSC::JSValue typeValue; - + if (declType != nullptr) { String typeStr = String::fromUTF8(declType); typeValue = JSC::jsNontrivialString(vm, typeStr); @@ -2480,10 +2480,10 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnDeclaredTypes, (JSGlobalObject * // If no declared type (e.g., for expressions or results of functions) typeValue = JSC::jsNull(); } - + array->putDirectIndex(lexicalGlobalObject, i, typeValue); } - + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(array)); } diff --git a/test/js/bun/sqlite/column-types.test.js b/test/js/bun/sqlite/column-types.test.js index 58ade55b88..1d93a845ad 100644 --- a/test/js/bun/sqlite/column-types.test.js +++ b/test/js/bun/sqlite/column-types.test.js @@ -1,5 +1,5 @@ -import { describe, it, expect } from "bun:test"; import { Database } from "bun:sqlite"; +import { describe, expect, it } from "bun:test"; describe("SQLite Statement column types", () => { it("reports correct column types for a variety of data types", () => { @@ -25,25 +25,25 @@ describe("SQLite Statement column types", () => { // Prepare a statement that selects all columns const stmt = db.prepare("SELECT * FROM test_types"); - + // Execute the statement to get column types const row = stmt.get(); // Verify column metadata expect(stmt.native.columns).toEqual(["id", "name", "weight", "image", "is_active"]); expect(stmt.native.columnsCount).toBe(5); - + // Test the columnTypes property (uses actual data types from sqlite3_column_type) expect(stmt.columnTypes).toBeDefined(); expect(Array.isArray(stmt.columnTypes)).toBe(true); expect(stmt.columnTypes.length).toBe(5); - expect(stmt.columnTypes).toEqual(['INTEGER', 'TEXT', 'FLOAT', 'BLOB', 'INTEGER']); - + expect(stmt.columnTypes).toEqual(["INTEGER", "TEXT", "FLOAT", "BLOB", "INTEGER"]); + // Test the declaredTypes property (uses declared types from sqlite3_column_decltype) expect(stmt.declaredTypes).toBeDefined(); expect(Array.isArray(stmt.declaredTypes)).toBe(true); expect(stmt.declaredTypes.length).toBe(5); - expect(stmt.declaredTypes).toEqual(['INTEGER', 'TEXT', 'REAL', 'BLOB', 'INTEGER']); + expect(stmt.declaredTypes).toEqual(["INTEGER", "TEXT", "REAL", "BLOB", "INTEGER"]); }); it("handles NULL values correctly", () => { @@ -59,15 +59,15 @@ describe("SQLite Statement column types", () => { db.run(`INSERT INTO nulls_test (id, nullable) VALUES (1, NULL)`); const stmt = db.prepare("SELECT * FROM nulls_test"); - + // Execute the statement to get column types const row = stmt.get(); // columnTypes now returns actual data types - NULL values are reported as 'NULL' - expect(stmt.columnTypes).toEqual(['INTEGER', 'NULL']); - + expect(stmt.columnTypes).toEqual(["INTEGER", "NULL"]); + // declaredTypes still shows the declared table schema - expect(stmt.declaredTypes).toEqual(['INTEGER', 'TEXT']); + expect(stmt.declaredTypes).toEqual(["INTEGER", "TEXT"]); }); it("reports actual column types based on data values", () => { @@ -82,24 +82,24 @@ describe("SQLite Statement column types", () => { // SQLite can store various types in the same column db.run(`INSERT INTO dynamic_types VALUES (1, 42)`); - + let stmt = db.prepare("SELECT * FROM dynamic_types"); - + // Execute the statement to get column types let row = stmt.get(); - + // We should get the actual type of the value (integer) - expect(stmt.columnTypes).toEqual(['INTEGER', 'INTEGER']); - + expect(stmt.columnTypes).toEqual(["INTEGER", "INTEGER"]); + // Update to a text value db.run(`UPDATE dynamic_types SET value = 'text' WHERE id = 1`); - + // Re-prepare to get fresh column type information stmt = db.prepare("SELECT * FROM dynamic_types"); row = stmt.get(); - + // We should get the actual type of the value (text) - expect(stmt.columnTypes).toEqual(['INTEGER', 'TEXT']); + expect(stmt.columnTypes).toEqual(["INTEGER", "TEXT"]); // Update to a float value db.run(`UPDATE dynamic_types SET value = 3.14 WHERE id = 1`); @@ -109,7 +109,7 @@ describe("SQLite Statement column types", () => { row = stmt.get(); // We should get the actual type of the value (float) - expect(stmt.columnTypes).toEqual(['INTEGER', 'FLOAT']); + expect(stmt.columnTypes).toEqual(["INTEGER", "FLOAT"]); }); it("reports actual types for columns from expressions", () => { @@ -124,14 +124,14 @@ describe("SQLite Statement column types", () => { expect(row).toEqual({ str_length: 3, magic_number: 42, - greeting: "hello" + greeting: "hello", }); // Check columns are correctly identified - expect(stmt.native.columns).toEqual(['str_length', 'magic_number', 'greeting']); - + expect(stmt.native.columns).toEqual(["str_length", "magic_number", "greeting"]); + // For expressions, expect the actual data types - expect(stmt.columnTypes).toEqual(['INTEGER', 'INTEGER', 'TEXT']); + expect(stmt.columnTypes).toEqual(["INTEGER", "INTEGER", "TEXT"]); }); it("handles multiple different expressions and functions", () => { @@ -148,33 +148,31 @@ describe("SQLite Statement column types", () => { length('bun') AS func_result, CURRENT_TIMESTAMP AS timestamp `); - + const row = stmt.get(); // Verify we have the expected columns expect(stmt.native.columns).toEqual([ - 'int_val', - 'float_val', - 'text_val', - 'blob_val', - 'null_val', - 'func_result', - 'timestamp' + "int_val", + "float_val", + "text_val", + "blob_val", + "null_val", + "func_result", + "timestamp", ]); // Expression columns should be reported with their actual types - expect(stmt.columnTypes).toEqual([ - 'INTEGER', 'FLOAT', 'TEXT', 'BLOB', 'NULL', 'INTEGER', 'TEXT' - ]); + expect(stmt.columnTypes).toEqual(["INTEGER", "FLOAT", "TEXT", "BLOB", "NULL", "INTEGER", "TEXT"]); // Verify data types were correctly identified at runtime - expect(typeof row.int_val).toBe('number'); - expect(typeof row.float_val).toBe('number'); - expect(typeof row.text_val).toBe('string'); + expect(typeof row.int_val).toBe("number"); + expect(typeof row.float_val).toBe("number"); + expect(typeof row.text_val).toBe("string"); expect(row.blob_val instanceof Uint8Array).toBe(true); expect(row.null_val).toBe(null); - expect(typeof row.func_result).toBe('number'); - expect(typeof row.timestamp).toBe('string'); + expect(typeof row.func_result).toBe("number"); + expect(typeof row.timestamp).toBe("string"); }); it("shows difference between columnTypes and declaredTypes for expressions", () => { @@ -185,8 +183,8 @@ describe("SQLite Statement column types", () => { const row = stmt.get(); // columnTypes shows actual data types based on the values - expect(stmt.columnTypes).toEqual(['INTEGER', 'INTEGER', 'TEXT']); - + expect(stmt.columnTypes).toEqual(["INTEGER", "INTEGER", "TEXT"]); + // declaredTypes shows declared types (which are null for expressions without explicit declarations) expect(stmt.declaredTypes).toEqual([null, null, null]); }); @@ -203,27 +201,27 @@ describe("SQLite Statement column types", () => { // Insert an integer value db.run(`INSERT INTO dynamic_types VALUES (1, 42)`); - + let stmt = db.prepare("SELECT * FROM dynamic_types"); let row = stmt.get(); - + // columnTypes shows actual type (integer) for the current value - expect(stmt.columnTypes).toEqual(['INTEGER', 'INTEGER']); - + expect(stmt.columnTypes).toEqual(["INTEGER", "INTEGER"]); + // declaredTypes shows the declared table schema - expect(stmt.declaredTypes).toEqual(['INTEGER', 'ANY']); - + expect(stmt.declaredTypes).toEqual(["INTEGER", "ANY"]); + // Update to a text value db.run(`UPDATE dynamic_types SET value = 'text' WHERE id = 1`); - + stmt = db.prepare("SELECT * FROM dynamic_types"); row = stmt.get(); - + // columnTypes now shows text for the current value - expect(stmt.columnTypes).toEqual(['INTEGER', 'TEXT']); - + expect(stmt.columnTypes).toEqual(["INTEGER", "TEXT"]); + // declaredTypes still shows the declared table schema - expect(stmt.declaredTypes).toEqual(['INTEGER', 'ANY']); + expect(stmt.declaredTypes).toEqual(["INTEGER", "ANY"]); }); it("throws an error when accessing columnTypes before statement execution", () => { @@ -235,7 +233,7 @@ describe("SQLite Statement column types", () => { // Accessing columnTypes before executing is fine (implicitly executes the statement) expect(stmt.columnTypes).toBeArray(); - + // Accessing declaredTypes before executing should throw expect(() => { stmt.declaredTypes;