diff --git a/change-notes/1.25/analysis-javascript.md b/change-notes/1.25/analysis-javascript.md index 487d067bc4a..283338144e3 100644 --- a/change-notes/1.25/analysis-javascript.md +++ b/change-notes/1.25/analysis-javascript.md @@ -10,7 +10,13 @@ - [jQuery](https://jquery.com/) - [marsdb](https://www.npmjs.com/package/marsdb) - [minimongo](https://www.npmjs.com/package/minimongo/) + - [mssql](https://www.npmjs.com/package/mssql) + - [mysql](https://www.npmjs.com/package/mysql) + - [pg](https://www.npmjs.com/package/pg) - [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) + - [sequelize](https://www.npmjs.com/package/sequelize) + - [spanner](https://www.npmjs.com/package/spanner) + - [sqlite](https://www.npmjs.com/package/sqlite) - [ssh2](https://www.npmjs.com/package/ssh2) - [ssh2-streams](https://www.npmjs.com/package/ssh2-streams) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll index 1844e5658c8..fb12c5ab55d 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll @@ -28,39 +28,44 @@ module SQL { * Provides classes modelling the (API compatible) `mysql` and `mysql2` packages. */ private module MySql { - /** Gets the package name `mysql` or `mysql2`. */ - string mysql() { result = "mysql" or result = "mysql2" } + private DataFlow::SourceNode mysql() { result = DataFlow::moduleImport(["mysql", "mysql2"]) } + + private DataFlow::CallNode createPool() { result = mysql().getAMemberCall("createPool") } + + /** Gets a reference to a MySQL pool. */ + private DataFlow::SourceNode pool(DataFlow::TypeTracker t) { + t.start() and + result = createPool() + or + exists(DataFlow::TypeTracker t2 | result = pool(t2).track(t2, t)) + } + + /** Gets a reference to a MySQL pool. */ + private DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) } /** Gets a call to `mysql.createConnection`. */ - DataFlow::SourceNode createConnection() { - result = DataFlow::moduleMember(mysql(), "createConnection").getACall() - } + DataFlow::CallNode createConnection() { result = mysql().getAMemberCall("createConnection") } - /** Gets a call to `mysql.createPool`. */ - DataFlow::SourceNode createPool() { - result = DataFlow::moduleMember(mysql(), "createPool").getACall() - } - - /** Gets a data flow node that contains a freshly created MySQL connection instance. */ - DataFlow::SourceNode connection() { - result = createConnection() + /** Gets a reference to a MySQL connection instance. */ + private DataFlow::SourceNode connection(DataFlow::TypeTracker t) { + t.start() and + ( + result = createConnection() + or + result = pool().getAMethodCall("getConnection").getABoundCallbackParameter(0, 1) + ) or - result = createPool().getAMethodCall("getConnection").getCallback(0).getParameter(1) + exists(DataFlow::TypeTracker t2 | result = connection(t2).track(t2, t)) } + /** Gets a reference to a MySQL connection instance. */ + DataFlow::SourceNode connection() { result = connection(DataFlow::TypeTracker::end()) } + /** A call to the MySql `query` method. */ - private class QueryCall extends DatabaseAccess, DataFlow::ValueNode { - override MethodCallExpr astNode; + private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { + QueryCall() { this = [pool(), connection()].getAMethodCall("query") } - QueryCall() { - exists(DataFlow::SourceNode recv | recv = createPool() or recv = connection() | - this = recv.getAMethodCall("query") - ) - } - - override DataFlow::Node getAQueryArgument() { - result = DataFlow::valueNode(astNode.getArgument(0)) - } + override DataFlow::Node getAQueryArgument() { result = getArgument(0) } } /** An expression that is passed to the `query` method and hence interpreted as SQL. */ @@ -69,20 +74,11 @@ private module MySql { } /** A call to the `escape` or `escapeId` method that performs SQL sanitization. */ - class EscapingSanitizer extends SQL::SqlSanitizer, @callexpr { + class EscapingSanitizer extends SQL::SqlSanitizer, MethodCallExpr { EscapingSanitizer() { - exists(string esc | esc = "escape" or esc = "escapeId" | - exists(DataFlow::SourceNode escape, MethodCallExpr mce | - escape = DataFlow::moduleMember(mysql(), esc) or - escape = connection().getAPropertyRead(esc) or - escape = createPool().getAPropertyRead(esc) - | - this = mce and - mce = escape.getACall().asExpr() and - input = mce.getArgument(0) and - output = mce - ) - ) + this = [mysql(), pool(), connection()].getAMethodCall(["escape", "escapeId"]).asExpr() and + input = this.getArgument(0) and + output = this } } @@ -91,9 +87,8 @@ private module MySql { string kind; Credentials() { - exists(DataFlow::SourceNode call, string prop | - (call = createConnection() or call = createPool()) and - call.asExpr().(CallExpr).hasOptionArgument(0, prop, this) and + exists(string prop | + this = [createConnection(), createPool()].getOptionArgument(0, prop).asExpr() and ( prop = "user" and kind = "user name" or @@ -110,21 +105,8 @@ private module MySql { * Provides classes modelling the `pg` package. */ private module Postgres { - /** Gets an expression of the form `new require('pg').Client()`. */ - DataFlow::SourceNode newClient() { - result = DataFlow::moduleImport("pg").getAConstructorInvocation("Client") - } - - /** Gets a data flow node that holds a freshly created Postgres client instance. */ - DataFlow::SourceNode client() { - result = newClient() - or - // pool.connect(function(err, client) { ... }) - result = newPool().getAMethodCall("connect").getCallback(0).getParameter(1) - } - /** Gets an expression that constructs a new connection pool. */ - DataFlow::SourceNode newPool() { + DataFlow::InvokeNode newPool() { // new require('pg').Pool() result = DataFlow::moduleImport("pg").getAConstructorInvocation("Pool") or @@ -132,26 +114,42 @@ private module Postgres { result = DataFlow::moduleImport("pg-pool").getAnInstantiation() } - private DataFlow::SourceNode clientOrPool(DataFlow::TypeTracker t) { + /** Gets a data flow node referring to a connection pool. */ + private DataFlow::SourceNode pool(DataFlow::TypeTracker t) { t.start() and - (result = client() or result = newPool()) + result = newPool() or - exists(DataFlow::TypeTracker t2 | result = clientOrPool(t2).track(t2, t)) + exists(DataFlow::TypeTracker t2 | result = pool(t2).track(t2, t)) } - private DataFlow::SourceNode clientOrPool() { - result = clientOrPool(DataFlow::TypeTracker::end()) + /** Gets a data flow node referring to a connection pool. */ + DataFlow::SourceNode pool() { result = pool(DataFlow::TypeTracker::end()) } + + /** Gets a creation of a Postgres client. */ + DataFlow::InvokeNode newClient() { + result = DataFlow::moduleImport("pg").getAConstructorInvocation("Client") } + /** Gets a data flow node referring to a Postgres client. */ + private DataFlow::SourceNode client(DataFlow::TypeTracker t) { + t.start() and + ( + result = newClient() + or + result = pool().getAMethodCall("connect").getABoundCallbackParameter(0, 1) + ) + or + exists(DataFlow::TypeTracker t2 | result = client(t2).track(t2, t)) + } + + /** Gets a data flow node referring to a Postgres client. */ + DataFlow::SourceNode client() { result = client(DataFlow::TypeTracker::end()) } + /** A call to the Postgres `query` method. */ - private class QueryCall extends DatabaseAccess, DataFlow::ValueNode { - override MethodCallExpr astNode; + private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { + QueryCall() { this = [client(), pool()].getAMethodCall("query") } - QueryCall() { this = clientOrPool().getAMethodCall("query") } - - override DataFlow::Node getAQueryArgument() { - result = DataFlow::valueNode(astNode.getArgument(0)) - } + override DataFlow::Node getAQueryArgument() { result = getArgument(0) } } /** An expression that is passed to the `query` method and hence interpreted as SQL. */ @@ -164,14 +162,10 @@ private module Postgres { string kind; Credentials() { - exists(DataFlow::InvokeNode call, string prop | - (call = newClient() or call = newPool()) and - this = call.getOptionArgument(0, prop).asExpr() and - ( - prop = "user" and kind = "user name" - or - prop = "password" and kind = prop - ) + exists(string prop | this = [newClient(), newPool()].getOptionArgument(0, prop).asExpr() | + prop = "user" and kind = "user name" + or + prop = "password" and kind = prop ) } @@ -196,10 +190,19 @@ private module Sqlite { result = sqlite().getAConstructorInvocation("Database") } - /** A call to a Sqlite query method. */ - private class QueryCall extends DatabaseAccess, DataFlow::ValueNode { - override MethodCallExpr astNode; + /** Gets a data flow node referring to a Sqlite database instance. */ + private DataFlow::SourceNode db(DataFlow::TypeTracker t) { + t.start() and + result = newDb() + or + exists(DataFlow::TypeTracker t2 | result = db(t2).track(t2, t)) + } + /** Gets a data flow node referring to a Sqlite database instance. */ + DataFlow::SourceNode db() { result = db(DataFlow::TypeTracker::end()) } + + /** A call to a Sqlite query method. */ + private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { QueryCall() { exists(string meth | meth = "all" or @@ -209,13 +212,11 @@ private module Sqlite { meth = "prepare" or meth = "run" | - this = newDb().getAMethodCall(meth) + this = db().getAMethodCall(meth) ) } - override DataFlow::Node getAQueryArgument() { - result = DataFlow::valueNode(astNode.getArgument(0)) - } + override DataFlow::Node getAQueryArgument() { result = getArgument(0) } } /** An expression that is passed to the `query` method and hence interpreted as SQL. */ @@ -229,17 +230,25 @@ private module Sqlite { */ private module MsSql { /** Gets a reference to the `mssql` module. */ - DataFlow::ModuleImportNode mssql() { result.getPath() = "mssql" } + DataFlow::SourceNode mssql() { result = DataFlow::moduleImport("mssql") } - /** Gets an expression that creates a request object. */ - DataFlow::SourceNode request() { - // new require('mssql').Request() - result = mssql().getAConstructorInvocation("Request") + /** Gets a data flow node referring to a request object. */ + private DataFlow::SourceNode request(DataFlow::TypeTracker t) { + t.start() and + ( + // new require('mssql').Request() + result = mssql().getAConstructorInvocation("Request") + or + // request.input(...) + result = request().getAMethodCall("input") + ) or - // request.input(...) - result = request().getAMethodCall("input") + exists(DataFlow::TypeTracker t2 | result = request(t2).track(t2, t)) } + /** Gets a data flow node referring to a request object. */ + DataFlow::SourceNode request() { result = request(DataFlow::TypeTracker::end()) } + /** A tagged template evaluated as a query. */ private class QueryTemplateExpr extends DatabaseAccess, DataFlow::ValueNode { override TaggedTemplateExpr astNode; @@ -252,16 +261,10 @@ private module MsSql { } /** A call to a MsSql query method. */ - private class QueryCall extends DatabaseAccess, DataFlow::ValueNode { - override MethodCallExpr astNode; + private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode { + QueryCall() { this = request().getAMethodCall(["query", "batch"]) } - QueryCall() { - exists(string meth | this = request().getAMethodCall(meth) | meth = "query" or meth = "batch") - } - - override DataFlow::Node getAQueryArgument() { - result = DataFlow::valueNode(astNode.getArgument(0)) - } + override DataFlow::Node getAQueryArgument() { result = getArgument(0) } } /** An expression that is passed to a method that interprets it as SQL. */ @@ -310,17 +313,22 @@ private module MsSql { * Provides classes modelling the `sequelize` package. */ private module Sequelize { - /** Gets an import of the `sequelize` module. */ - DataFlow::ModuleImportNode sequelize() { result.getPath() = "sequelize" } + /** Gets a node referring to an instance of the `Sequelize` class. */ + private DataFlow::SourceNode sequelize(DataFlow::TypeTracker t) { + t.start() and + result = DataFlow::moduleImport("sequelize").getAnInstantiation() + or + exists(DataFlow::TypeTracker t2 | result = sequelize(t2).track(t2, t)) + } - /** Gets an expression that creates an instance of the `Sequelize` class. */ - DataFlow::SourceNode newSequelize() { result = sequelize().getAnInstantiation() } + /** Gets a node referring to an instance of the `Sequelize` class. */ + DataFlow::SourceNode sequelize() { result = sequelize(DataFlow::TypeTracker::end()) } /** A call to `Sequelize.query`. */ private class QueryCall extends DatabaseAccess, DataFlow::ValueNode { override MethodCallExpr astNode; - QueryCall() { this = newSequelize().getAMethodCall("query") } + QueryCall() { this = sequelize().getAMethodCall("query") } override DataFlow::Node getAQueryArgument() { result = DataFlow::valueNode(astNode.getArgument(0)) @@ -341,7 +349,7 @@ private module Sequelize { Credentials() { exists(NewExpr ne, string prop | - ne = newSequelize().asExpr() and + ne = sequelize().asExpr() and ( this = ne.getArgument(1) and prop = "username" or @@ -376,27 +384,61 @@ private module Spanner { result = DataFlow::moduleMember("@google-cloud/spanner", "Spanner") } - /** - * Gets a node that refers to an instance of the `Database` class. - */ - DataFlow::SourceNode database() { - result = spanner().getAnInvocation().getAMethodCall("instance").getAMethodCall("database") + /** Gets a data flow node referring to the result of `Spanner()` or `new Spanner()`. */ + private DataFlow::SourceNode spannerNew(DataFlow::TypeTracker t) { + t.start() and + result = spanner().getAnInvocation() + or + exists(DataFlow::TypeTracker t2 | result = spannerNew(t2).track(t2, t)) } - /** - * Gets a node that refers to an instance of the `v1.SpannerClient` class. - */ - DataFlow::SourceNode v1SpannerClient() { + /** Gets a data flow node referring to the result of `Spanner()` or `new Spanner()`. */ + DataFlow::SourceNode spannerNew() { result = spannerNew(DataFlow::TypeTracker::end()) } + + /** Gets a data flow node referring to the result of `.instance()`. */ + private DataFlow::SourceNode instance(DataFlow::TypeTracker t) { + t.start() and + result = spannerNew().getAMethodCall("instance") + or + exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) + } + + /** Gets a data flow node referring to the result of `.instance()`. */ + DataFlow::SourceNode instance() { result = instance(DataFlow::TypeTracker::end()) } + + /** Gets a node that refers to an instance of the `Database` class. */ + private DataFlow::SourceNode database(DataFlow::TypeTracker t) { + t.start() and + result = instance().getAMethodCall("database") + or + exists(DataFlow::TypeTracker t2 | result = database(t2).track(t2, t)) + } + + /** Gets a node that refers to an instance of the `Database` class. */ + DataFlow::SourceNode database() { result = database(DataFlow::TypeTracker::end()) } + + /** Gets a node that refers to an instance of the `v1.SpannerClient` class. */ + private DataFlow::SourceNode v1SpannerClient(DataFlow::TypeTracker t) { + t.start() and result = spanner().getAPropertyRead("v1").getAPropertyRead("SpannerClient").getAnInstantiation() + or + exists(DataFlow::TypeTracker t2 | result = v1SpannerClient(t2).track(t2, t)) } - /** - * Gets a node that refers to a transaction object. - */ - DataFlow::SourceNode transaction() { - result = database().getAMethodCall("runTransaction").getCallback(0).getParameter(1) + /** Gets a node that refers to an instance of the `v1.SpannerClient` class. */ + DataFlow::SourceNode v1SpannerClient() { result = v1SpannerClient(DataFlow::TypeTracker::end()) } + + /** Gets a node that refers to a transaction object. */ + private DataFlow::SourceNode transaction(DataFlow::TypeTracker t) { + t.start() and + result = database().getAMethodCall("runTransaction").getABoundCallbackParameter(0, 1) + or + exists(DataFlow::TypeTracker t2 | result = transaction(t2).track(t2, t)) } + /** Gets a node that refers to a transaction object. */ + DataFlow::SourceNode transaction() { result = transaction(DataFlow::TypeTracker::end()) } + /** * A call to a Spanner method that executes a SQL query. */ @@ -418,9 +460,7 @@ private module Spanner { */ class DatabaseRunCall extends SqlExecution { DatabaseRunCall() { - exists(string run | run = "run" or run = "runPartitionedUpdate" or run = "runStream" | - this = database().getAMethodCall(run) - ) + this = database().getAMethodCall(["run", "runPartitionedUpdate", "runStream"]) } } @@ -428,11 +468,7 @@ private module Spanner { * A call to `Transaction.run`, `Transaction.runStream` or `Transaction.runUpdate`. */ class TransactionRunCall extends SqlExecution { - TransactionRunCall() { - exists(string run | run = "run" or run = "runStream" or run = "runUpdate" | - this = transaction().getAMethodCall(run) - ) - } + TransactionRunCall() { this = transaction().getAMethodCall(["run", "runStream", "runUpdate"]) } } /** @@ -440,9 +476,7 @@ private module Spanner { */ class ExecuteSqlCall extends SqlExecution { ExecuteSqlCall() { - exists(string exec | exec = "executeSql" or exec = "executeStreamingSql" | - this = v1SpannerClient().getAMethodCall(exec) - ) + this = v1SpannerClient().getAMethodCall(["executeSql", "executeStreamingSql"]) } override DataFlow::Node getAQueryArgument() { diff --git a/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected b/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected index c9f43c1d487..69675073081 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected +++ b/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected @@ -2,6 +2,7 @@ | mssql1.js:7:75:7:79 | value | | mssql2.js:5:15:5:34 | 'select 1 as number' | | mssql2.js:13:15:13:66 | 'create ... table' | +| mssql2.js:22:24:22:43 | 'select 1 as number' | | mysql1.js:13:18:13:43 | 'SELECT ... lution' | | mysql1.js:18:18:22:1 | {\\n s ... vid']\\n} | | mysql2.js:12:12:12:37 | 'SELECT ... lution' | @@ -9,12 +10,15 @@ | mysql2tst.js:23:3:23:56 | 'SELECT ... e` > ?' | | mysql3.js:14:20:14:52 | 'SELECT ... etable' | | mysql4.js:14:18:14:20 | sql | +| mysqlImport.js:3:18:5:1 | {\\n s ... = ?',\\n} | | postgres1.js:37:21:37:24 | text | | postgres2.js:30:16:30:41 | 'SELECT ... number' | | postgres3.js:15:16:15:40 | 'SELECT ... s name' | | postgres5.js:8:21:8:25 | query | +| postgresImport.js:4:18:4:43 | 'SELECT ... number' | | sequelize2.js:10:17:10:118 | 'SELECT ... Y name' | | sequelize.js:8:17:8:118 | 'SELECT ... Y name' | +| sequelizeImport.js:3:17:3:118 | 'SELECT ... Y name' | | spanner2.js:5:26:5:35 | "SQL code" | | spanner2.js:7:35:7:44 | "SQL code" | | spanner.js:6:8:6:17 | "SQL code" | @@ -35,4 +39,6 @@ | spanner.js:18:16:18:25 | "SQL code" | | spanner.js:19:16:19:34 | { sql: "SQL code" } | | spanner.js:19:23:19:32 | "SQL code" | +| spannerImport.js:4:8:4:17 | "SQL code" | | sqlite.js:7:8:7:45 | "UPDATE ... id = ?" | +| sqliteImport.js:2:8:2:44 | "UPDATE ... id = ?" | diff --git a/javascript/ql/test/library-tests/frameworks/SQL/mssql2.js b/javascript/ql/test/library-tests/frameworks/SQL/mssql2.js index fe0cdfa5b55..9b64f06068a 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/mssql2.js +++ b/javascript/ql/test/library-tests/frameworks/SQL/mssql2.js @@ -13,3 +13,13 @@ request.query('select 1 as number', (err, result) => { request.batch('create procedure #temporary as select * from table', (err, result) => { // ... error checks }) + +class C { + constructor(req) { + this.req = req; + } + send() { + this.req.query('select 1 as number', (err, result) => {}) + } +} +new C(new sql.Request()); diff --git a/javascript/ql/test/library-tests/frameworks/SQL/mysql1.js b/javascript/ql/test/library-tests/frameworks/SQL/mysql1.js index 3642bde09a3..b759e3ef8fb 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/mysql1.js +++ b/javascript/ql/test/library-tests/frameworks/SQL/mysql1.js @@ -26,3 +26,5 @@ connection.query({ }); connection.end(); + +exports.connection = connection; diff --git a/javascript/ql/test/library-tests/frameworks/SQL/mysqlImport.js b/javascript/ql/test/library-tests/frameworks/SQL/mysqlImport.js new file mode 100644 index 00000000000..6fc2535fdec --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/SQL/mysqlImport.js @@ -0,0 +1,6 @@ +const { connection } = require("./mysql1"); + +connection.query({ + sql: 'SELECT * FROM `books` WHERE `author` = ?', +}, function (error, results, fields) { +}); diff --git a/javascript/ql/test/library-tests/frameworks/SQL/postgres1.js b/javascript/ql/test/library-tests/frameworks/SQL/postgres1.js index 02c27fffa5b..ad2ad68a891 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/postgres1.js +++ b/javascript/ql/test/library-tests/frameworks/SQL/postgres1.js @@ -36,3 +36,5 @@ module.exports.query = function (text, values, callback) { console.log('query:', text, values); return pool.query(text, values, callback); }; + +module.exports.pool = pool; diff --git a/javascript/ql/test/library-tests/frameworks/SQL/postgresImport.js b/javascript/ql/test/library-tests/frameworks/SQL/postgresImport.js new file mode 100644 index 00000000000..68f47c2ab5d --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/SQL/postgresImport.js @@ -0,0 +1,6 @@ +const { pool } = require("./postgres1"); + +pool.connect((err, client, done) => { + client.query('SELECT $1::int AS number', ['1'], function(err, result) { + }); +}); diff --git a/javascript/ql/test/library-tests/frameworks/SQL/sequelize.js b/javascript/ql/test/library-tests/frameworks/SQL/sequelize.js index 4121366a1e9..8e9313c4bf3 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/sequelize.js +++ b/javascript/ql/test/library-tests/frameworks/SQL/sequelize.js @@ -7,3 +7,5 @@ const sequelize = new Sequelize('database', 'username', 'password', { }); sequelize.query('SELECT * FROM Products WHERE (name LIKE \'%' + criteria + '%\') AND deletedAt IS NULL) ORDER BY name'); + +exports.sequelize = sequelize; diff --git a/javascript/ql/test/library-tests/frameworks/SQL/sequelizeImport.js b/javascript/ql/test/library-tests/frameworks/SQL/sequelizeImport.js new file mode 100644 index 00000000000..6fed34df310 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/SQL/sequelizeImport.js @@ -0,0 +1,3 @@ +const { sequelize } = require("./sequelize"); + +sequelize.query('SELECT * FROM Products WHERE (name LIKE \'%' + criteria + '%\') AND deletedAt IS NULL) ORDER BY name'); diff --git a/javascript/ql/test/library-tests/frameworks/SQL/spanner.js b/javascript/ql/test/library-tests/frameworks/SQL/spanner.js index d4276223006..a27c52d82fd 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/spanner.js +++ b/javascript/ql/test/library-tests/frameworks/SQL/spanner.js @@ -17,4 +17,6 @@ db.runTransaction((err, tx) => { tx.runStream({ sql: "SQL code" }); tx.runUpdate("SQL code"); tx.runUpdate({ sql: "SQL code" }); -}); \ No newline at end of file +}); + +exports.instance = instance; diff --git a/javascript/ql/test/library-tests/frameworks/SQL/spannerImport.js b/javascript/ql/test/library-tests/frameworks/SQL/spannerImport.js new file mode 100644 index 00000000000..fdf08dfdc5b --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/SQL/spannerImport.js @@ -0,0 +1,4 @@ +const { instance } = require('./spanner'); +const db = instance.database('db'); + +db.run("SQL code", (err, rows) => {}); diff --git a/javascript/ql/test/library-tests/frameworks/SQL/sqlite.js b/javascript/ql/test/library-tests/frameworks/SQL/sqlite.js index 1a7a63e1604..e2f072902d0 100644 --- a/javascript/ql/test/library-tests/frameworks/SQL/sqlite.js +++ b/javascript/ql/test/library-tests/frameworks/SQL/sqlite.js @@ -5,3 +5,5 @@ var sqlite = require('sqlite3'); var db = new sqlite.Database(":memory:"); db.run("UPDATE tbl SET name = ? WHERE id = ?", "bar", 2); + +exports.db = db; diff --git a/javascript/ql/test/library-tests/frameworks/SQL/sqliteImport.js b/javascript/ql/test/library-tests/frameworks/SQL/sqliteImport.js new file mode 100644 index 00000000000..fb1539c73a1 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/SQL/sqliteImport.js @@ -0,0 +1,2 @@ +const { db } = require('./sqlite'); +db.run("UPDATE foo SET bar = ? WHERE id = ?", "bar", 2);