mirror of https://github.com/nodejs/node.git
sqlite: add StatementSync.prototype.columns()
This commit adds a method for retrieving column metadata from a prepared statement. Fixes: https://github.com/nodejs/node/issues/57457 PR-URL: https://github.com/nodejs/node/pull/57490 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
This commit is contained in:
parent
f4b835c734
commit
dd38a47ea1
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
'defines': [
|
||||
'SQLITE_DEFAULT_MEMSTATUS=0',
|
||||
'SQLITE_ENABLE_COLUMN_METADATA',
|
||||
'SQLITE_ENABLE_MATH_FUNCTIONS',
|
||||
'SQLITE_ENABLE_SESSION',
|
||||
'SQLITE_ENABLE_PREUPDATE_HOOK'
|
||||
|
|
|
@ -8,6 +8,7 @@ template("sqlite_gn_build") {
|
|||
config("sqlite_config") {
|
||||
include_dirs = [ "." ]
|
||||
defines = [
|
||||
"SQLITE_ENABLE_COLUMN_METADATA",
|
||||
"SQLITE_ENABLE_MATH_FUNCTIONS",
|
||||
"SQLITE_ENABLE_SESSION",
|
||||
"SQLITE_ENABLE_PREUPDATE_HOOK",
|
||||
|
|
|
@ -373,6 +373,34 @@ objects. If the prepared statement does not return any results, this method
|
|||
returns an empty array. The prepared statement [parameters are bound][] using
|
||||
the values in `namedParameters` and `anonymousParameters`.
|
||||
|
||||
### `statement.columns()`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns: {Array} An array of objects. Each object corresponds to a column
|
||||
in the prepared statement, and contains the following properties:
|
||||
|
||||
* `column`: {string|null} The unaliased name of the column in the origin
|
||||
table, or `null` if the column is the result of an expression or subquery.
|
||||
This property is the result of [`sqlite3_column_origin_name()`][].
|
||||
* `database`: {string|null} The unaliased name of the origin database, or
|
||||
`null` if the column is the result of an expression or subquery. This
|
||||
property is the result of [`sqlite3_column_database_name()`][].
|
||||
* `name`: {string} The name assigned to the column in the result set of a
|
||||
`SELECT` statement. This property is the result of
|
||||
[`sqlite3_column_name()`][].
|
||||
* `table`: {string|null} The unaliased name of the origin table, or `null` if
|
||||
the column is the result of an expression or subquery. This property is the
|
||||
result of [`sqlite3_column_table_name()`][].
|
||||
* `type`: {string|null} The declared data type of the column, or `null` if the
|
||||
column is the result of an expression or subquery. This property is the
|
||||
result of [`sqlite3_column_decltype()`][].
|
||||
|
||||
This method is used to retrieve information about the columns returned by the
|
||||
prepared statement.
|
||||
|
||||
### `statement.expandedSQL`
|
||||
|
||||
<!-- YAML
|
||||
|
@ -687,6 +715,11 @@ resolution handler passed to [`database.applyChangeset()`][]. See also
|
|||
[`sqlite3_backup_step()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||
[`sqlite3_changes64()`]: https://www.sqlite.org/c3ref/changes.html
|
||||
[`sqlite3_close_v2()`]: https://www.sqlite.org/c3ref/close.html
|
||||
[`sqlite3_column_database_name()`]: https://www.sqlite.org/c3ref/column_database_name.html
|
||||
[`sqlite3_column_decltype()`]: https://www.sqlite.org/c3ref/column_decltype.html
|
||||
[`sqlite3_column_name()`]: https://www.sqlite.org/c3ref/column_name.html
|
||||
[`sqlite3_column_origin_name()`]: https://www.sqlite.org/c3ref/column_database_name.html
|
||||
[`sqlite3_column_table_name()`]: https://www.sqlite.org/c3ref/column_database_name.html
|
||||
[`sqlite3_create_function_v2()`]: https://www.sqlite.org/c3ref/create_function.html
|
||||
[`sqlite3_exec()`]: https://www.sqlite.org/c3ref/exec.html
|
||||
[`sqlite3_expanded_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
V(crypto_rsa_pss_string, "rsa-pss") \
|
||||
V(cwd_string, "cwd") \
|
||||
V(data_string, "data") \
|
||||
V(database_string, "database") \
|
||||
V(default_is_true_string, "defaultIsTrue") \
|
||||
V(deserialize_info_string, "deserializeInfo") \
|
||||
V(dest_string, "dest") \
|
||||
|
@ -370,6 +371,7 @@
|
|||
V(subject_string, "subject") \
|
||||
V(subjectaltname_string, "subjectaltname") \
|
||||
V(syscall_string, "syscall") \
|
||||
V(table_string, "table") \
|
||||
V(target_string, "target") \
|
||||
V(thread_id_string, "threadId") \
|
||||
V(ticketkeycallback_string, "onticketkeycallback") \
|
||||
|
|
|
@ -158,6 +158,16 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
|
|||
}
|
||||
}
|
||||
|
||||
inline MaybeLocal<Value> NullableSQLiteStringToValue(Isolate* isolate,
|
||||
const char* str) {
|
||||
if (str == nullptr) {
|
||||
return Null(isolate);
|
||||
}
|
||||
|
||||
return String::NewFromUtf8(isolate, str, NewStringType::kInternalized)
|
||||
.As<Value>();
|
||||
}
|
||||
|
||||
class BackupJob : public ThreadPoolWork {
|
||||
public:
|
||||
explicit BackupJob(Environment* env,
|
||||
|
@ -1897,6 +1907,72 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
|
|||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void StatementSync::Columns(const FunctionCallbackInfo<Value>& args) {
|
||||
StatementSync* stmt;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
THROW_AND_RETURN_ON_BAD_STATE(
|
||||
env, stmt->IsFinalized(), "statement has been finalized");
|
||||
int num_cols = sqlite3_column_count(stmt->statement_);
|
||||
Isolate* isolate = env->isolate();
|
||||
LocalVector<Value> cols(isolate);
|
||||
LocalVector<Name> col_keys(isolate,
|
||||
{env->column_string(),
|
||||
env->database_string(),
|
||||
env->name_string(),
|
||||
env->table_string(),
|
||||
env->type_string()});
|
||||
Local<Value> value;
|
||||
|
||||
cols.reserve(num_cols);
|
||||
for (int i = 0; i < num_cols; ++i) {
|
||||
LocalVector<Value> col_values(isolate);
|
||||
col_values.reserve(col_keys.size());
|
||||
|
||||
if (!NullableSQLiteStringToValue(
|
||||
isolate, sqlite3_column_origin_name(stmt->statement_, i))
|
||||
.ToLocal(&value)) {
|
||||
return;
|
||||
}
|
||||
col_values.emplace_back(value);
|
||||
|
||||
if (!NullableSQLiteStringToValue(
|
||||
isolate, sqlite3_column_database_name(stmt->statement_, i))
|
||||
.ToLocal(&value)) {
|
||||
return;
|
||||
}
|
||||
col_values.emplace_back(value);
|
||||
|
||||
if (!stmt->ColumnNameToName(i).ToLocal(&value)) {
|
||||
return;
|
||||
}
|
||||
col_values.emplace_back(value);
|
||||
|
||||
if (!NullableSQLiteStringToValue(
|
||||
isolate, sqlite3_column_table_name(stmt->statement_, i))
|
||||
.ToLocal(&value)) {
|
||||
return;
|
||||
}
|
||||
col_values.emplace_back(value);
|
||||
|
||||
if (!NullableSQLiteStringToValue(
|
||||
isolate, sqlite3_column_decltype(stmt->statement_, i))
|
||||
.ToLocal(&value)) {
|
||||
return;
|
||||
}
|
||||
col_values.emplace_back(value);
|
||||
|
||||
Local<Object> column = Object::New(isolate,
|
||||
Null(isolate),
|
||||
col_keys.data(),
|
||||
col_values.data(),
|
||||
col_keys.size());
|
||||
cols.emplace_back(column);
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(Array::New(isolate, cols.data(), cols.size()));
|
||||
}
|
||||
|
||||
void StatementSync::SourceSQLGetter(const FunctionCallbackInfo<Value>& args) {
|
||||
StatementSync* stmt;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
|
||||
|
@ -2002,6 +2078,8 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
|
|||
SetProtoMethod(isolate, tmpl, "all", StatementSync::All);
|
||||
SetProtoMethod(isolate, tmpl, "get", StatementSync::Get);
|
||||
SetProtoMethod(isolate, tmpl, "run", StatementSync::Run);
|
||||
SetProtoMethodNoSideEffect(
|
||||
isolate, tmpl, "columns", StatementSync::Columns);
|
||||
SetSideEffectFreeGetter(isolate,
|
||||
tmpl,
|
||||
FIXED_ONE_BYTE_STRING(isolate, "sourceSQL"),
|
||||
|
|
|
@ -116,6 +116,7 @@ class StatementSync : public BaseObject {
|
|||
static void Iterate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Get(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Run(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Columns(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SourceSQLGetter(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void ExpandedSQLGetter(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('node:assert');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
const { suite, test } = require('node:test');
|
||||
|
||||
suite('StatementSync.prototype.columns()', () => {
|
||||
test('returns column metadata for core SQLite types', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec(`CREATE TABLE test (
|
||||
col1 INTEGER,
|
||||
col2 REAL,
|
||||
col3 TEXT,
|
||||
col4 BLOB,
|
||||
col5 NULL
|
||||
)`);
|
||||
const stmt = db.prepare('SELECT col1, col2, col3, col4, col5 FROM test');
|
||||
assert.deepStrictEqual(stmt.columns(), [
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'col1',
|
||||
database: 'main',
|
||||
name: 'col1',
|
||||
table: 'test',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'col2',
|
||||
database: 'main',
|
||||
name: 'col2',
|
||||
table: 'test',
|
||||
type: 'REAL',
|
||||
},
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'col3',
|
||||
database: 'main',
|
||||
name: 'col3',
|
||||
table: 'test',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'col4',
|
||||
database: 'main',
|
||||
name: 'col4',
|
||||
table: 'test',
|
||||
type: 'BLOB',
|
||||
},
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'col5',
|
||||
database: 'main',
|
||||
name: 'col5',
|
||||
table: 'test',
|
||||
type: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('supports statements using multiple tables', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec(`
|
||||
CREATE TABLE test1 (value1 INTEGER);
|
||||
CREATE TABLE test2 (value2 INTEGER);
|
||||
`);
|
||||
const stmt = db.prepare('SELECT value1, value2 FROM test1, test2');
|
||||
assert.deepStrictEqual(stmt.columns(), [
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'value1',
|
||||
database: 'main',
|
||||
name: 'value1',
|
||||
table: 'test1',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'value2',
|
||||
database: 'main',
|
||||
name: 'value2',
|
||||
table: 'test2',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('supports column aliases', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec(`CREATE TABLE test (value INTEGER)`);
|
||||
const stmt = db.prepare('SELECT value AS foo FROM test');
|
||||
assert.deepStrictEqual(stmt.columns(), [
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'value',
|
||||
database: 'main',
|
||||
name: 'foo',
|
||||
table: 'test',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('supports column expressions', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec(`CREATE TABLE test (value INTEGER)`);
|
||||
const stmt = db.prepare('SELECT value + 1, value FROM test');
|
||||
assert.deepStrictEqual(stmt.columns(), [
|
||||
{
|
||||
__proto__: null,
|
||||
column: null,
|
||||
database: null,
|
||||
name: 'value + 1',
|
||||
table: null,
|
||||
type: null,
|
||||
},
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'value',
|
||||
database: 'main',
|
||||
name: 'value',
|
||||
table: 'test',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('supports subqueries', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec(`CREATE TABLE test (value INTEGER)`);
|
||||
const stmt = db.prepare('SELECT * FROM (SELECT * FROM test)');
|
||||
assert.deepStrictEqual(stmt.columns(), [
|
||||
{
|
||||
__proto__: null,
|
||||
column: 'value',
|
||||
database: 'main',
|
||||
name: 'value',
|
||||
table: 'test',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('supports statements that do not return data', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec('CREATE TABLE test (value INTEGER)');
|
||||
const stmt = db.prepare('INSERT INTO test (value) VALUES (?)');
|
||||
assert.deepStrictEqual(stmt.columns(), []);
|
||||
});
|
||||
|
||||
test('throws if the statement is finalized', () => {
|
||||
const db = new DatabaseSync(':memory:');
|
||||
db.exec('CREATE TABLE test (value INTEGER)');
|
||||
const stmt = db.prepare('SELECT value FROM test');
|
||||
db.close();
|
||||
assert.throws(() => {
|
||||
stmt.columns();
|
||||
}, /statement has been finalized/);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue