diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 8bb774ee50b1df..e311c64938d50b 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -223,9 +223,40 @@ void DatabaseSync::Exec(const FunctionCallbackInfo& args) { } void DatabaseSync::CreateSession(const FunctionCallbackInfo& args) { + std::string table; + + Environment* env = Environment::GetCurrent(args); + if (args.Length() > 0) { + if (!args[0]->IsObject()) { + node::THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), "The \"options\" argument must be an object."); + return; + } + + Local options = args[0].As(); + + Local table_key = String::NewFromUtf8( + env->isolate(), + "table", + v8::NewStringType::kNormal).ToLocalChecked(); + if (options->HasOwnProperty(env->context(), table_key).FromJust()) { + Local table_value = options->Get( + env->context(), + table_key).ToLocalChecked(); + + if (table_value->IsString()) { + String::Utf8Value str(env->isolate(), table_value); + table = std::string(*str); + } else { + node::THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), "The \"options.table\" argument must be a string."); + return; + } + } + } + DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); - Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, db->connection_ == nullptr, "database is not open"); @@ -235,8 +266,7 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo& args) { CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); db->sessions_.insert(pSession); - // TODO(louwers): allow specifying table name - r = sqlite3session_attach(pSession, nullptr); + r = sqlite3session_attach(pSession, table == "" ? nullptr : table.c_str()); CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); @@ -770,7 +800,7 @@ void Session::Changeset(const v8::FunctionCallbackInfo& args) { Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); - sqlite3 *db = session->database_ ? session->database_->connection_ : nullptr; + sqlite3* db = session->database_ ? session->database_->connection_ : nullptr; THROW_AND_RETURN_ON_BAD_STATE(env, db == nullptr, "database is not open"); int nChangeset; diff --git a/test/parallel/test-sqlite.js b/test/parallel/test-sqlite.js index 19646db76c13c4..a26f84e23f257c 100644 --- a/test/parallel/test-sqlite.js +++ b/test/parallel/test-sqlite.js @@ -757,68 +757,121 @@ test('PRAGMAs are supported', (t) => { ); }); -test('creating and applying a changeset', (t) => { - const createDatabase = () => { - const database = new DatabaseSync(':memory:'); - database.exec(` +suite('session extension', () => { + test('creating and applying a changeset', (t) => { + const createDataTableSql = ` CREATE TABLE data( key INTEGER PRIMARY KEY, value TEXT - ) STRICT - `); - return database; - }; + ) STRICT`; - const databaseFrom = createDatabase(); - const session = databaseFrom.createSession(); + const createDatabase = () => { + const database = new DatabaseSync(':memory:'); + database.exec(createDataTableSql); + return database; + }; - const select = 'SELECT * FROM data ORDER BY key'; + const databaseFrom = createDatabase(); + const session = databaseFrom.createSession(); - const insert = databaseFrom.prepare('INSERT INTO data (key, value) VALUES (?, ?)'); - insert.run(1, 'hello'); - insert.run(2, 'world'); + const select = 'SELECT * FROM data ORDER BY key'; - const databaseTo = createDatabase(); + const insert = databaseFrom.prepare('INSERT INTO data (key, value) VALUES (?, ?)'); + insert.run(1, 'hello'); + insert.run(2, 'world'); - databaseTo.applyChangeset(session.changeset()); - t.assert.deepStrictEqual( - databaseFrom.prepare(select).all(), - databaseTo.prepare(select).all() - ); -}); + const databaseTo = createDatabase(); -test('trying to create session when database is closed results in exception', (t) => { - const database = new DatabaseSync(':memory:'); - database.close(); - t.assert.throws(() => { - database.createSession(); - }, { - name: 'Error', - message: 'database is not open', + databaseTo.applyChangeset(session.changeset()); + t.assert.deepStrictEqual( + databaseFrom.prepare(select).all(), + databaseTo.prepare(select).all() + ); }); -}); -test('trying to create changeset when database is closed results in exception', (t) => { - const database = new DatabaseSync(':memory:'); - const session = database.createSession(); - database.close(); - t.assert.throws(() => { - session.changeset(); - }, { - name: 'Error', - message: 'database is not open', + test('trying to create session when database is closed results in exception', (t) => { + const database = new DatabaseSync(':memory:'); + database.close(); + t.assert.throws(() => { + database.createSession(); + }, { + name: 'Error', + message: 'database is not open', + }); }); -}); -test('trying to apply a changeset when database is closed results in exception', (t) => { - const database = new DatabaseSync(':memory:'); - const session = database.createSession(); - const changeset = session.changeset(); - database.close(); - t.assert.throws(() => { - database.applyChangeset(changeset); - }, { - name: 'Error', - message: 'database is not open', + test('trying to create changeset when database is closed results in exception', (t) => { + const database = new DatabaseSync(':memory:'); + const session = database.createSession(); + database.close(); + t.assert.throws(() => { + session.changeset(); + }, { + name: 'Error', + message: 'database is not open', + }); + }); + + test('trying to apply a changeset when database is closed results in exception', (t) => { + const database = new DatabaseSync(':memory:'); + const session = database.createSession(); + const changeset = session.changeset(); + database.close(); + t.assert.throws(() => { + database.applyChangeset(changeset); + }, { + name: 'Error', + message: 'database is not open', + }); + }); + + test('set table with wrong type when creating session', (t) => { + const database = new DatabaseSync(':memory:'); + t.assert.throws(() => { + database.createSession({ + table: true + }); + }, { + name: 'TypeError', + message: 'The "table" property must be a string.' + }); }); -}); \ No newline at end of file + + test('setting options.table causes only one table to be tracked', (t) => { + const database1 = new DatabaseSync(':memory:'); + const database2 = new DatabaseSync(':memory:'); + + const createData1TableSql = `CREATE TABLE data1 ( + key INTEGER PRIMARY KEY, + value TEXT + ) STRICT + `; + const createData2TableSql = `CREATE TABLE data2 ( + key INTEGER PRIMARY KEY, + value TEXT + ) STRICT + `; + database1.exec(createData1TableSql); + database1.exec(createData2TableSql); + database2.exec(createData1TableSql); + database2.exec(createData2TableSql); + + const session = database1.createSession({ + table: 'data1' + }); + const insert1 = database1.prepare('INSERT INTO data1 (key, value) VALUES (?, ?)'); + insert1.run(1, 'hello'); + insert1.run(2, 'world'); + const insert2 = database1.prepare('INSERT INTO data2 (key, value) VALUES (?, ?)'); + insert2.run(1, 'hello'); + insert2.run(2, 'world'); + const select1 = 'SELECT * FROM data1 ORDER BY key'; + const select2 = 'SELECT * FROM data2 ORDER BY key'; + database2.applyChangeset(session.changeset()); + t.assert.deepStrictEqual( + database1.prepare(select1).all(), + database2.prepare(select1).all()); // data1 table should be equal + t.assert.deepStrictEqual(database2.prepare(select2).all(), []); // data2 should be empty in database2 + t.assert.strictEqual(database1.prepare(select2).all().length, 2); // data1 should have values in database1 + }); +});