| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4e24456 commit c510391
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ | |||
| 14 | 14 | }, | |
| 15 | 15 | 'defines': [ | |
| 16 | 16 | 'SQLITE_DEFAULT_MEMSTATUS=0', | |
| 17 | + 'SQLITE_ENABLE_COLUMN_METADATA', | ||
| 17 | 18 | 'SQLITE_ENABLE_MATH_FUNCTIONS', | |
| 18 | 19 | 'SQLITE_ENABLE_SESSION', | |
| 19 | 20 | 'SQLITE_ENABLE_PREUPDATE_HOOK' | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ template("sqlite_gn_build") { | |||
| 8 | 8 | config("sqlite_config") { | |
| 9 | 9 | include_dirs = [ "." ] | |
| 10 | 10 | defines = [ | |
| 11 | + "SQLITE_ENABLE_COLUMN_METADATA", | ||
| 11 | 12 | "SQLITE_ENABLE_MATH_FUNCTIONS", | |
| 12 | 13 | "SQLITE_ENABLE_SESSION", | |
| 13 | 14 | "SQLITE_ENABLE_PREUPDATE_HOOK", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -383,6 +383,34 @@ objects. If the prepared statement does not return any results, this method | |||
| 383 | 383 | returns an empty array. The prepared statement [parameters are bound][] using | |
| 384 | 384 | the values in `namedParameters` and `anonymousParameters`. | |
| 385 | 385 | ||
| 386 | + ### `statement.columns()` | ||
| 387 | + | ||
| 388 | + <!-- YAML | ||
| 389 | + added: REPLACEME | ||
| 390 | + --> | ||
| 391 | + | ||
| 392 | + * Returns: {Array} An array of objects. Each object corresponds to a column | ||
| 393 | + in the prepared statement, and contains the following properties: | ||
| 394 | + | ||
| 395 | + * `column`: {string|null} The unaliased name of the column in the origin | ||
| 396 | + table, or `null` if the column is the result of an expression or subquery. | ||
| 397 | + This property is the result of [`sqlite3_column_origin_name()`][]. | ||
| 398 | + * `database`: {string|null} The unaliased name of the origin database, or | ||
| 399 | + `null` if the column is the result of an expression or subquery. This | ||
| 400 | + property is the result of [`sqlite3_column_database_name()`][]. | ||
| 401 | + * `name`: {string} The name assigned to the column in the result set of a | ||
| 402 | + `SELECT` statement. This property is the result of | ||
| 403 | + [`sqlite3_column_name()`][]. | ||
| 404 | + * `table`: {string|null} The unaliased name of the origin table, or `null` if | ||
| 405 | + the column is the result of an expression or subquery. This property is the | ||
| 406 | + result of [`sqlite3_column_table_name()`][]. | ||
| 407 | + * `type`: {string|null} The declared data type of the column, or `null` if the | ||
| 408 | + column is the result of an expression or subquery. This property is the | ||
| 409 | + result of [`sqlite3_column_decltype()`][]. | ||
| 410 | + | ||
| 411 | + This method is used to retrieve information about the columns returned by the | ||
| 412 | + prepared statement. | ||
| 413 | + | ||
| 386 | 414 | ### `statement.expandedSQL` | |
| 387 | 415 | ||
| 388 | 416 | <!-- YAML | |
@@ -694,6 +722,11 @@ resolution handler passed to [`database.applyChangeset()`][]. See also | |||
| 694 | 722 | [`sqlite3_backup_step()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep | |
| 695 | 723 | [`sqlite3_changes64()`]: https://www.sqlite.org/c3ref/changes.html | |
| 696 | 724 | [`sqlite3_close_v2()`]: https://www.sqlite.org/c3ref/close.html | |
| 725 | + [`sqlite3_column_database_name()`]: https://www.sqlite.org/c3ref/column_database_name.html | ||
| 726 | + [`sqlite3_column_decltype()`]: https://www.sqlite.org/c3ref/column_decltype.html | ||
| 727 | + [`sqlite3_column_name()`]: https://www.sqlite.org/c3ref/column_name.html | ||
| 728 | + [`sqlite3_column_origin_name()`]: https://www.sqlite.org/c3ref/column_database_name.html | ||
| 729 | + [`sqlite3_column_table_name()`]: https://www.sqlite.org/c3ref/column_database_name.html | ||
| 697 | 730 | [`sqlite3_create_function_v2()`]: https://www.sqlite.org/c3ref/create_function.html | |
| 698 | 731 | [`sqlite3_exec()`]: https://www.sqlite.org/c3ref/exec.html | |
| 699 | 732 | [`sqlite3_expanded_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -117,6 +117,7 @@ | |||
| 117 | 117 | V(crypto_rsa_pss_string, "rsa-pss") \ | |
| 118 | 118 | V(cwd_string, "cwd") \ | |
| 119 | 119 | V(data_string, "data") \ | |
| 120 | + V(database_string, "database") \ | ||
| 120 | 121 | V(default_is_true_string, "defaultIsTrue") \ | |
| 121 | 122 | V(deserialize_info_string, "deserializeInfo") \ | |
| 122 | 123 | V(dest_string, "dest") \ | |
@@ -362,6 +363,7 @@ | |||
| 362 | 363 | V(subject_string, "subject") \ | |
| 363 | 364 | V(subjectaltname_string, "subjectaltname") \ | |
| 364 | 365 | V(syscall_string, "syscall") \ | |
| 366 | + V(table_string, "table") \ | ||
| 365 | 367 | V(target_string, "target") \ | |
| 366 | 368 | V(thread_id_string, "threadId") \ | |
| 367 | 369 | V(ticketkeycallback_string, "onticketkeycallback") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -158,6 +158,16 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) { | |||
| 158 | 158 | } | |
| 159 | 159 | } | |
| 160 | 160 | ||
| 161 | + inline MaybeLocal<Value> NullableSQLiteStringToValue(Isolate* isolate, | ||
| 162 | + const char* str) { | ||
| 163 | + if (str == nullptr) { | ||
| 164 | + return Null(isolate); | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + return String::NewFromUtf8(isolate, str, NewStringType::kInternalized) | ||
| 168 | + .As<Value>(); | ||
| 169 | + } | ||
| 170 | + | ||
| 161 | 171 | class BackupJob : public ThreadPoolWork { | |
| 162 | 172 | public: | |
| 163 | 173 | explicit BackupJob(Environment* env, | |
@@ -1918,6 +1928,72 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) { | |||
| 1918 | 1928 | args.GetReturnValue().Set(result); | |
| 1919 | 1929 | } | |
| 1920 | 1930 | ||
| 1931 | + void StatementSync::Columns(const FunctionCallbackInfo<Value>& args) { | ||
| 1932 | + StatementSync* stmt; | ||
| 1933 | + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); | ||
| 1934 | + Environment* env = Environment::GetCurrent(args); | ||
| 1935 | + THROW_AND_RETURN_ON_BAD_STATE( | ||
| 1936 | + env, stmt->IsFinalized(), "statement has been finalized"); | ||
| 1937 | + int num_cols = sqlite3_column_count(stmt->statement_); | ||
| 1938 | + Isolate* isolate = env->isolate(); | ||
| 1939 | + LocalVector<Value> cols(isolate); | ||
| 1940 | + LocalVector<Name> col_keys(isolate, | ||
| 1941 | + {env->column_string(), | ||
| 1942 | + env->database_string(), | ||
| 1943 | + env->name_string(), | ||
| 1944 | + env->table_string(), | ||
| 1945 | + env->type_string()}); | ||
| 1946 | + Local<Value> value; | ||
| 1947 | + | ||
| 1948 | + cols.reserve(num_cols); | ||
| 1949 | + for (int i = 0; i < num_cols; ++i) { | ||
| 1950 | + LocalVector<Value> col_values(isolate); | ||
| 1951 | + col_values.reserve(col_keys.size()); | ||
| 1952 | + | ||
| 1953 | + if (!NullableSQLiteStringToValue( | ||
| 1954 | + isolate, sqlite3_column_origin_name(stmt->statement_, i)) | ||
| 1955 | + .ToLocal(&value)) { | ||
| 1956 | + return; | ||
| 1957 | + } | ||
| 1958 | + col_values.emplace_back(value); | ||
| 1959 | + | ||
| 1960 | + if (!NullableSQLiteStringToValue( | ||
| 1961 | + isolate, sqlite3_column_database_name(stmt->statement_, i)) | ||
| 1962 | + .ToLocal(&value)) { | ||
| 1963 | + return; | ||
| 1964 | + } | ||
| 1965 | + col_values.emplace_back(value); | ||
| 1966 | + | ||
| 1967 | + if (!stmt->ColumnNameToName(i).ToLocal(&value)) { | ||
| 1968 | + return; | ||
| 1969 | + } | ||
| 1970 | + col_values.emplace_back(value); | ||
| 1971 | + | ||
| 1972 | + if (!NullableSQLiteStringToValue( | ||
| 1973 | + isolate, sqlite3_column_table_name(stmt->statement_, i)) | ||
| 1974 | + .ToLocal(&value)) { | ||
| 1975 | + return; | ||
| 1976 | + } | ||
| 1977 | + col_values.emplace_back(value); | ||
| 1978 | + | ||
| 1979 | + if (!NullableSQLiteStringToValue( | ||
| 1980 | + isolate, sqlite3_column_decltype(stmt->statement_, i)) | ||
| 1981 | + .ToLocal(&value)) { | ||
| 1982 | + return; | ||
| 1983 | + } | ||
| 1984 | + col_values.emplace_back(value); | ||
| 1985 | + | ||
| 1986 | + Local<Object> column = Object::New(isolate, | ||
| 1987 | + Null(isolate), | ||
| 1988 | + col_keys.data(), | ||
| 1989 | + col_values.data(), | ||
| 1990 | + col_keys.size()); | ||
| 1991 | + cols.emplace_back(column); | ||
| 1992 | + } | ||
| 1993 | + | ||
| 1994 | + args.GetReturnValue().Set(Array::New(isolate, cols.data(), cols.size())); | ||
| 1995 | + } | ||
| 1996 | + | ||
| 1921 | 1997 | void StatementSync::SourceSQLGetter(const FunctionCallbackInfo<Value>& args) { | |
| 1922 | 1998 | StatementSync* stmt; | |
| 1923 | 1999 | ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); | |
@@ -2040,6 +2116,8 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate( | |||
| 2040 | 2116 | SetProtoMethod(isolate, tmpl, "all", StatementSync::All); | |
| 2041 | 2117 | SetProtoMethod(isolate, tmpl, "get", StatementSync::Get); | |
| 2042 | 2118 | SetProtoMethod(isolate, tmpl, "run", StatementSync::Run); | |
| 2119 | + SetProtoMethodNoSideEffect( | ||
| 2120 | + isolate, tmpl, "columns", StatementSync::Columns); | ||
| 2043 | 2121 | SetSideEffectFreeGetter(isolate, | |
| 2044 | 2122 | tmpl, | |
| 2045 | 2123 | FIXED_ONE_BYTE_STRING(isolate, "sourceSQL"), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -119,6 +119,7 @@ class StatementSync : public BaseObject { | |||
| 119 | 119 | static void Iterate(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 120 | 120 | static void Get(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 121 | 121 | static void Run(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 122 | + static void Columns(const v8::FunctionCallbackInfo<v8::Value>& args); | ||
| 122 | 123 | static void SourceSQLGetter(const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 123 | 124 | static void ExpandedSQLGetter( | |
| 124 | 125 | const v8::FunctionCallbackInfo<v8::Value>& args); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,161 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + require('../common'); | ||
| 3 | + const assert = require('node:assert'); | ||
| 4 | + const { DatabaseSync } = require('node:sqlite'); | ||
| 5 | + const { suite, test } = require('node:test'); | ||
| 6 | + | ||
| 7 | + suite('StatementSync.prototype.columns()', () => { | ||
| 8 | + test('returns column metadata for core SQLite types', () => { | ||
| 9 | + const db = new DatabaseSync(':memory:'); | ||
| 10 | + db.exec(`CREATE TABLE test ( | ||
| 11 | + col1 INTEGER, | ||
| 12 | + col2 REAL, | ||
| 13 | + col3 TEXT, | ||
| 14 | + col4 BLOB, | ||
| 15 | + col5 NULL | ||
| 16 | + )`); | ||
| 17 | + const stmt = db.prepare('SELECT col1, col2, col3, col4, col5 FROM test'); | ||
| 18 | + assert.deepStrictEqual(stmt.columns(), [ | ||
| 19 | + { | ||
| 20 | + __proto__: null, | ||
| 21 | + column: 'col1', | ||
| 22 | + database: 'main', | ||
| 23 | + name: 'col1', | ||
| 24 | + table: 'test', | ||
| 25 | + type: 'INTEGER', | ||
| 26 | + }, | ||
| 27 | + { | ||
| 28 | + __proto__: null, | ||
| 29 | + column: 'col2', | ||
| 30 | + database: 'main', | ||
| 31 | + name: 'col2', | ||
| 32 | + table: 'test', | ||
| 33 | + type: 'REAL', | ||
| 34 | + }, | ||
| 35 | + { | ||
| 36 | + __proto__: null, | ||
| 37 | + column: 'col3', | ||
| 38 | + database: 'main', | ||
| 39 | + name: 'col3', | ||
| 40 | + table: 'test', | ||
| 41 | + type: 'TEXT', | ||
| 42 | + }, | ||
| 43 | + { | ||
| 44 | + __proto__: null, | ||
| 45 | + column: 'col4', | ||
| 46 | + database: 'main', | ||
| 47 | + name: 'col4', | ||
| 48 | + table: 'test', | ||
| 49 | + type: 'BLOB', | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + __proto__: null, | ||
| 53 | + column: 'col5', | ||
| 54 | + database: 'main', | ||
| 55 | + name: 'col5', | ||
| 56 | + table: 'test', | ||
| 57 | + type: null, | ||
| 58 | + }, | ||
| 59 | + ]); | ||
| 60 | + }); | ||
| 61 | + | ||
| 62 | + test('supports statements using multiple tables', () => { | ||
| 63 | + const db = new DatabaseSync(':memory:'); | ||
| 64 | + db.exec(` | ||
| 65 | + CREATE TABLE test1 (value1 INTEGER); | ||
| 66 | + CREATE TABLE test2 (value2 INTEGER); | ||
| 67 | + `); | ||
| 68 | + const stmt = db.prepare('SELECT value1, value2 FROM test1, test2'); | ||
| 69 | + assert.deepStrictEqual(stmt.columns(), [ | ||
| 70 | + { | ||
| 71 | + __proto__: null, | ||
| 72 | + column: 'value1', | ||
| 73 | + database: 'main', | ||
| 74 | + name: 'value1', | ||
| 75 | + table: 'test1', | ||
| 76 | + type: 'INTEGER', | ||
| 77 | + }, | ||
| 78 | + { | ||
| 79 | + __proto__: null, | ||
| 80 | + column: 'value2', | ||
| 81 | + database: 'main', | ||
| 82 | + name: 'value2', | ||
| 83 | + table: 'test2', | ||
| 84 | + type: 'INTEGER', | ||
| 85 | + }, | ||
| 86 | + ]); | ||
| 87 | + }); | ||
| 88 | + | ||
| 89 | + test('supports column aliases', () => { | ||
| 90 | + const db = new DatabaseSync(':memory:'); | ||
| 91 | + db.exec(`CREATE TABLE test (value INTEGER)`); | ||
| 92 | + const stmt = db.prepare('SELECT value AS foo FROM test'); | ||
| 93 | + assert.deepStrictEqual(stmt.columns(), [ | ||
| 94 | + { | ||
| 95 | + __proto__: null, | ||
| 96 | + column: 'value', | ||
| 97 | + database: 'main', | ||
| 98 | + name: 'foo', | ||
| 99 | + table: 'test', | ||
| 100 | + type: 'INTEGER', | ||
| 101 | + }, | ||
| 102 | + ]); | ||
| 103 | + }); | ||
| 104 | + | ||
| 105 | + test('supports column expressions', () => { | ||
| 106 | + const db = new DatabaseSync(':memory:'); | ||
| 107 | + db.exec(`CREATE TABLE test (value INTEGER)`); | ||
| 108 | + const stmt = db.prepare('SELECT value + 1, value FROM test'); | ||
| 109 | + assert.deepStrictEqual(stmt.columns(), [ | ||
| 110 | + { | ||
| 111 | + __proto__: null, | ||
| 112 | + column: null, | ||
| 113 | + database: null, | ||
| 114 | + name: 'value + 1', | ||
| 115 | + table: null, | ||
| 116 | + type: null, | ||
| 117 | + }, | ||
| 118 | + { | ||
| 119 | + __proto__: null, | ||
| 120 | + column: 'value', | ||
| 121 | + database: 'main', | ||
| 122 | + name: 'value', | ||
| 123 | + table: 'test', | ||
| 124 | + type: 'INTEGER', | ||
| 125 | + }, | ||
| 126 | + ]); | ||
| 127 | + }); | ||
| 128 | + | ||
| 129 | + test('supports subqueries', () => { | ||
| 130 | + const db = new DatabaseSync(':memory:'); | ||
| 131 | + db.exec(`CREATE TABLE test (value INTEGER)`); | ||
| 132 | + const stmt = db.prepare('SELECT * FROM (SELECT * FROM test)'); | ||
| 133 | + assert.deepStrictEqual(stmt.columns(), [ | ||
| 134 | + { | ||
| 135 | + __proto__: null, | ||
| 136 | + column: 'value', | ||
| 137 | + database: 'main', | ||
| 138 | + name: 'value', | ||
| 139 | + table: 'test', | ||
| 140 | + type: 'INTEGER', | ||
| 141 | + }, | ||
| 142 | + ]); | ||
| 143 | + }); | ||
| 144 | + | ||
| 145 | + test('supports statements that do not return data', () => { | ||
| 146 | + const db = new DatabaseSync(':memory:'); | ||
| 147 | + db.exec('CREATE TABLE test (value INTEGER)'); | ||
| 148 | + const stmt = db.prepare('INSERT INTO test (value) VALUES (?)'); | ||
| 149 | + assert.deepStrictEqual(stmt.columns(), []); | ||
| 150 | + }); | ||
| 151 | + | ||
| 152 | + test('throws if the statement is finalized', () => { | ||
| 153 | + const db = new DatabaseSync(':memory:'); | ||
| 154 | + db.exec('CREATE TABLE test (value INTEGER)'); | ||
| 155 | + const stmt = db.prepare('SELECT value FROM test'); | ||
| 156 | + db.close(); | ||
| 157 | + assert.throws(() => { | ||
| 158 | + stmt.columns(); | ||
| 159 | + }, /statement has been finalized/); | ||
| 160 | + }); | ||
| 161 | + }); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments