JavaScript: Add support for Google Cloud Spanner.

This commit is contained in:
Max Schaefer
2018-10-10 15:04:16 +01:00
parent c064b1f41d
commit cd284b2f97
5 changed files with 150 additions and 0 deletions

View File

@@ -8,6 +8,7 @@
* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following features:
- file system access, for example through [fs-extra](https://github.com/jprichardson/node-fs-extra) or [globby](https://www.npmjs.com/package/globby)
- the [Google Cloud Spanner](https://cloud.google.com/spanner) database
* The type inference now handles nested imports (that is, imports not appearing at the toplevel). This may yield fewer false-positive results on projects that use this non-standard language feature.

View File

@@ -387,3 +387,105 @@ private module Sequelize {
}
}
}
/**
* Provides classes modelling the Google Cloud Spanner library.
*/
private module Spanner {
/**
* Gets a node that refers to the `Spanner` class
*/
DataFlow::SourceNode spanner() {
// older versions
result = DataFlow::moduleImport("@google-cloud/spanner")
or
// newer versions
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 node that refers to an instance of the `v1.SpannerClient` class.
*/
DataFlow::SourceNode v1SpannerClient() {
result = spanner().getAPropertyRead("v1").getAPropertyRead("SpannerClient").getAnInstantiation()
}
/**
* Gets a node that refers to a transaction object.
*/
DataFlow::SourceNode transaction() {
result = database().getAMethodCall("runTransaction").getCallback(0).getParameter(1)
}
/**
* A call to a Spanner method that executes a SQL query.
*/
abstract class SqlExecution extends DatabaseAccess, DataFlow::InvokeNode {
/**
* Gets the position of the query argument; default is zero, which can be overridden
* by subclasses.
*/
int getQueryArgumentPosition() {
result = 0
}
override DataFlow::Node getAQueryArgument() {
result = getArgument(getQueryArgumentPosition()) or
result = getOptionArgument(getQueryArgumentPosition(), "sql")
}
}
/**
* A call to `Database.run` or `Database.runStream`.
*/
class DatabaseRunCall extends SqlExecution {
DatabaseRunCall() {
exists (string run | run = "run" or run = "runPartitionedUpdate" or run = "runStream" |
this = database().getAMethodCall(run)
)
}
}
/**
* A call to `Transaction.run` or `Database.runStream`.
*/
class TransactionRunCall extends SqlExecution {
TransactionRunCall() {
exists (string run | run = "run" or run = "runStream" or run = "runUpdate" |
this = transaction().getAMethodCall(run)
)
}
}
/**
* A call to `v1.SpannerClient.executeSql` or `v1.SpannerClient.executeStreamingSql`.
*/
class ExecuteSqlCall extends SqlExecution {
ExecuteSqlCall() {
exists (string exec | exec = "executeSql" or exec = "executeStreamingSql" |
this = v1SpannerClient().getAMethodCall(exec)
)
}
override DataFlow::Node getAQueryArgument() {
// `executeSql` and `executeStreamingSql` do not accept query strings directly
result = getOptionArgument(0, "sql")
}
}
/**
* An expression that is interpreted as a SQL string.
*/
class QueryString extends SQL::SqlString {
QueryString() {
this = any(SqlExecution se).getAQueryArgument().asExpr()
}
}
}

View File

@@ -14,4 +14,24 @@
| postgres3.js:15:16:15:40 | 'SELECT ... s name' |
| sequelize2.js:10:17:10:118 | 'SELECT ... Y name' |
| sequelize.js:8:17:8: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" |
| spanner.js:7:8:7:26 | { sql: "SQL code" } |
| spanner.js:7:15:7:24 | "SQL code" |
| spanner.js:8:25:8:34 | "SQL code" |
| spanner.js:9:25:9:43 | { sql: "SQL code" } |
| spanner.js:9:32:9:41 | "SQL code" |
| spanner.js:10:14:10:23 | "SQL code" |
| spanner.js:11:14:11:31 | { sql: "SQL code"} |
| spanner.js:11:21:11:30 | "SQL code" |
| spanner.js:14:10:14:19 | "SQL code" |
| spanner.js:15:10:15:28 | { sql: "SQL code" } |
| spanner.js:15:17:15:26 | "SQL code" |
| spanner.js:16:16:16:25 | "SQL code" |
| spanner.js:17:16:17:34 | { sql: "SQL code" } |
| spanner.js:17:23:17:32 | "SQL code" |
| 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" |
| sqlite.js:7:8:7:45 | "UPDATE ... id = ?" |

View File

@@ -0,0 +1,20 @@
const { Spanner } = require("@google-cloud/spanner");
const spanner = new Spanner();
const instance = spanner.instance('inst');
const db = instance.database('db');
db.run("SQL code", (err, rows) => {});
db.run({ sql: "SQL code" }, (err, rows) => {});
db.runPartitionedUpdate("SQL code", (err, rows) => {});
db.runPartitionedUpdate({ sql: "SQL code" }, (err, rows) => {});
db.runStream("SQL code");
db.runStream({ sql: "SQL code"});
db.runTransaction((err, tx) => {
tx.run("SQL code");
tx.run({ sql: "SQL code" });
tx.runStream("SQL code");
tx.runStream({ sql: "SQL code" });
tx.runUpdate("SQL code");
tx.runUpdate({ sql: "SQL code" });
});

View File

@@ -0,0 +1,7 @@
const spanner = require("@google-cloud/spanner");
const client = new spanner.v1.SpannerClient({});
client.executeSql("not SQL code", (err, rows) => {});
client.executeSql({ sql: "SQL code" }, (err, rows) => {});
client.executeStreamingSql("not SQL code", (err, rows) => {});
client.executeStreamingSql({ sql: "SQL code" }, (err, rows) => {});