Skip to content

Commit c1098c5

Browse files
authored
feat: add support for QueryOptions (#846)
* feat: add backend query options * feat: add support for query options * fix: pass default query options to tx * fix: remove unnecessary code * samples: add samples for query options * fix(tests): fix failing samples test * fix: rebase on latest generated code
1 parent faf224d commit c1098c5

File tree

14 files changed

+743
-20
lines changed

14 files changed

+743
-20
lines changed

proto/spanner.d.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,9 @@ export namespace google {
11561156

11571157
/** ExecuteSqlRequest seqno */
11581158
seqno?: (number|Long|null);
1159+
1160+
/** ExecuteSqlRequest queryOptions */
1161+
queryOptions?: (google.spanner.v1.ExecuteSqlRequest.IQueryOptions|null);
11591162
}
11601163

11611164
/** Represents an ExecuteSqlRequest. */
@@ -1194,6 +1197,9 @@ export namespace google {
11941197
/** ExecuteSqlRequest seqno. */
11951198
public seqno: (number|Long);
11961199

1200+
/** ExecuteSqlRequest queryOptions. */
1201+
public queryOptions?: (google.spanner.v1.ExecuteSqlRequest.IQueryOptions|null);
1202+
11971203
/**
11981204
* Creates a new ExecuteSqlRequest instance using the specified properties.
11991205
* @param [properties] Properties to set
@@ -1267,6 +1273,96 @@ export namespace google {
12671273

12681274
namespace ExecuteSqlRequest {
12691275

1276+
/** Properties of a QueryOptions. */
1277+
interface IQueryOptions {
1278+
1279+
/** QueryOptions optimizerVersion */
1280+
optimizerVersion?: (string|null);
1281+
}
1282+
1283+
/** Represents a QueryOptions. */
1284+
class QueryOptions implements IQueryOptions {
1285+
1286+
/**
1287+
* Constructs a new QueryOptions.
1288+
* @param [properties] Properties to set
1289+
*/
1290+
constructor(properties?: google.spanner.v1.ExecuteSqlRequest.IQueryOptions);
1291+
1292+
/** QueryOptions optimizerVersion. */
1293+
public optimizerVersion: string;
1294+
1295+
/**
1296+
* Creates a new QueryOptions instance using the specified properties.
1297+
* @param [properties] Properties to set
1298+
* @returns QueryOptions instance
1299+
*/
1300+
public static create(properties?: google.spanner.v1.ExecuteSqlRequest.IQueryOptions): google.spanner.v1.ExecuteSqlRequest.QueryOptions;
1301+
1302+
/**
1303+
* Encodes the specified QueryOptions message. Does not implicitly {@link google.spanner.v1.ExecuteSqlRequest.QueryOptions.verify|verify} messages.
1304+
* @param message QueryOptions message or plain object to encode
1305+
* @param [writer] Writer to encode to
1306+
* @returns Writer
1307+
*/
1308+
public static encode(message: google.spanner.v1.ExecuteSqlRequest.IQueryOptions, writer?: $protobuf.Writer): $protobuf.Writer;
1309+
1310+
/**
1311+
* Encodes the specified QueryOptions message, length delimited. Does not implicitly {@link google.spanner.v1.ExecuteSqlRequest.QueryOptions.verify|verify} messages.
1312+
* @param message QueryOptions message or plain object to encode
1313+
* @param [writer] Writer to encode to
1314+
* @returns Writer
1315+
*/
1316+
public static encodeDelimited(message: google.spanner.v1.ExecuteSqlRequest.IQueryOptions, writer?: $protobuf.Writer): $protobuf.Writer;
1317+
1318+
/**
1319+
* Decodes a QueryOptions message from the specified reader or buffer.
1320+
* @param reader Reader or buffer to decode from
1321+
* @param [length] Message length if known beforehand
1322+
* @returns QueryOptions
1323+
* @throws {Error} If the payload is not a reader or valid buffer
1324+
* @throws {$protobuf.util.ProtocolError} If required fields are missing
1325+
*/
1326+
public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): google.spanner.v1.ExecuteSqlRequest.QueryOptions;
1327+
1328+
/**
1329+
* Decodes a QueryOptions message from the specified reader or buffer, length delimited.
1330+
* @param reader Reader or buffer to decode from
1331+
* @returns QueryOptions
1332+
* @throws {Error} If the payload is not a reader or valid buffer
1333+
* @throws {$protobuf.util.ProtocolError} If required fields are missing
1334+
*/
1335+
public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): google.spanner.v1.ExecuteSqlRequest.QueryOptions;
1336+
1337+
/**
1338+
* Verifies a QueryOptions message.
1339+
* @param message Plain object to verify
1340+
* @returns `null` if valid, otherwise the reason why it is not
1341+
*/
1342+
public static verify(message: { [k: string]: any }): (string|null);
1343+
1344+
/**
1345+
* Creates a QueryOptions message from a plain object. Also converts values to their respective internal types.
1346+
* @param object Plain object
1347+
* @returns QueryOptions
1348+
*/
1349+
public static fromObject(object: { [k: string]: any }): google.spanner.v1.ExecuteSqlRequest.QueryOptions;
1350+
1351+
/**
1352+
* Creates a plain object from a QueryOptions message. Also converts values to other types if specified.
1353+
* @param message QueryOptions
1354+
* @param [options] Conversion options
1355+
* @returns Plain object
1356+
*/
1357+
public static toObject(message: google.spanner.v1.ExecuteSqlRequest.QueryOptions, options?: $protobuf.IConversionOptions): { [k: string]: any };
1358+
1359+
/**
1360+
* Converts this QueryOptions to JSON.
1361+
* @returns JSON object
1362+
*/
1363+
public toJSON(): { [k: string]: any };
1364+
}
1365+
12701366
/** QueryMode enum. */
12711367
enum QueryMode {
12721368
NORMAL = 0,

samples/queryoptions.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
async function databaseWithQueryOptions(instanceId, databaseId, projectId) {
18+
// [START spanner_create_client_with_query_options]
19+
// Imports the Google Cloud client library
20+
const {Spanner} = require('@google-cloud/spanner');
21+
22+
/**
23+
* TODO(developer): Uncomment the following lines before running the sample.
24+
*/
25+
// const projectId = 'my-project-id';
26+
// const instanceId = 'my-instance';
27+
// const databaseId = 'my-database';
28+
29+
// Creates a client
30+
const spanner = new Spanner({
31+
projectId: projectId,
32+
});
33+
34+
// Gets a reference to a Cloud Spanner instance and database
35+
const instance = spanner.instance(instanceId);
36+
const database = instance.database(databaseId, {}, {optimizerVersion: '1'});
37+
38+
const query = {
39+
sql: `SELECT AlbumId, AlbumTitle, MarketingBudget
40+
FROM Albums
41+
ORDER BY AlbumTitle`,
42+
};
43+
44+
// Queries rows from the Albums table
45+
try {
46+
const [rows] = await database.run(query);
47+
48+
rows.forEach(row => {
49+
const json = row.toJSON();
50+
const marketingBudget = json.MarketingBudget
51+
? json.MarketingBudget
52+
: null; // This value is nullable
53+
console.log(
54+
`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`
55+
);
56+
});
57+
} catch (err) {
58+
console.error('ERROR:', err);
59+
} finally {
60+
// Close the database when finished.
61+
database.close();
62+
}
63+
// [END spanner_create_client_with_query_options]
64+
}
65+
66+
async function queryWithQueryOptions(instanceId, databaseId, projectId) {
67+
// [START spanner_query_with_query_options]
68+
// Imports the Google Cloud client library
69+
const {Spanner} = require('@google-cloud/spanner');
70+
71+
/**
72+
* TODO(developer): Uncomment the following lines before running the sample.
73+
*/
74+
// const projectId = 'my-project-id';
75+
// const instanceId = 'my-instance';
76+
// const databaseId = 'my-database';
77+
78+
// Creates a client
79+
const spanner = new Spanner({
80+
projectId: projectId,
81+
});
82+
83+
// Gets a reference to a Cloud Spanner instance and database
84+
const instance = spanner.instance(instanceId);
85+
const database = instance.database(databaseId);
86+
87+
const query = {
88+
sql: `SELECT AlbumId, AlbumTitle, MarketingBudget
89+
FROM Albums
90+
ORDER BY AlbumTitle`,
91+
queryOptions: {
92+
optimizerVersion: 'latest',
93+
},
94+
};
95+
96+
// Queries rows from the Albums table
97+
try {
98+
const [rows] = await database.run(query);
99+
100+
rows.forEach(row => {
101+
const json = row.toJSON();
102+
const marketingBudget = json.MarketingBudget
103+
? json.MarketingBudget
104+
: null; // This value is nullable
105+
console.log(
106+
`AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`
107+
);
108+
});
109+
} catch (err) {
110+
console.error('ERROR:', err);
111+
} finally {
112+
// Close the database when finished.
113+
database.close();
114+
}
115+
// [END spanner_query_with_query_options]
116+
}
117+
118+
require(`yargs`)
119+
.demand(1)
120+
.command(
121+
`databaseWithQueryOptions <instanceName> <databaseName> <projectId>`,
122+
`Gets a database reference with default query options and executes a query`,
123+
{},
124+
opts =>
125+
databaseWithQueryOptions(
126+
opts.instanceName,
127+
opts.databaseName,
128+
opts.projectId
129+
)
130+
)
131+
.command(
132+
`queryWithQueryOptions <instanceName> <databaseName> <projectId>`,
133+
`Executes a query using specific query options`,
134+
{},
135+
opts =>
136+
queryWithQueryOptions(
137+
opts.instanceName,
138+
opts.databaseName,
139+
opts.projectId
140+
)
141+
)
142+
.example(
143+
`node $0 databaseWithQueryOptions "my-instance" "my-database" "my-project-id"`
144+
)
145+
.example(
146+
`node $0 queryWithQueryOptions "my-instance" "my-database" "my-project-id"`
147+
)
148+
.wrap(120)
149+
.recommendCommands()
150+
.epilogue(`For more information, see https://cloud.google.com/spanner/docs`)
151+
.strict()
152+
.help().argv;

samples/system-test/spanner.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const batchCmd = `node batch.js`;
2525
const crudCmd = `node crud.js`;
2626
const schemaCmd = `node schema.js`;
2727
const indexingCmd = `node indexing.js`;
28+
const queryOptionsCmd = `node queryoptions.js`;
2829
const transactionCmd = `node transaction.js`;
2930
const timestampCmd = `node timestamp.js`;
3031
const structCmd = `node struct.js`;
@@ -269,6 +270,28 @@ describe('Spanner', () => {
269270
assert.match(output, /AlbumId: 1, AlbumTitle: Total Junk/);
270271
});
271272

273+
// spanner_create_client_with_query_options
274+
it(`should use query options from a database reference`, async () => {
275+
const output = execSync(
276+
`${queryOptionsCmd} databaseWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`
277+
);
278+
assert.match(
279+
output,
280+
/AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/
281+
);
282+
});
283+
284+
// spanner_query_with_query_options
285+
it(`should use query options on request`, async () => {
286+
const output = execSync(
287+
`${queryOptionsCmd} queryWithQueryOptions ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`
288+
);
289+
assert.match(
290+
output,
291+
/AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget:/
292+
);
293+
});
294+
272295
// read_only_transaction
273296
it(`should read an example table using transactions`, async () => {
274297
const output = execSync(

src/database.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ export interface CancelableDuplex extends Duplex {
187187
* @param {string} name Name of the database.
188188
* @param {SessionPoolOptions|SessionPoolInterface} options Session pool
189189
* configuration options or custom pool interface.
190+
* @param {spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions} queryOptions
191+
* The default query options to use for queries on the database.
190192
*
191193
* @example
192194
* const {Spanner} = require('@google-cloud/spanner');
@@ -197,14 +199,16 @@ export interface CancelableDuplex extends Duplex {
197199
class Database extends GrpcServiceObject {
198200
formattedName_: string;
199201
pool_: SessionPoolInterface;
202+
queryOptions_?: spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions;
200203
request: <T, R = void>(
201204
config: RequestConfig,
202205
callback: RequestCallback<T, R>
203206
) => void;
204207
constructor(
205208
instance: Instance,
206209
name: string,
207-
poolOptions?: SessionPoolConstructor | SessionPoolOptions
210+
poolOptions?: SessionPoolConstructor | SessionPoolOptions,
211+
queryOptions?: spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions
208212
) {
209213
const methods = {
210214
/**
@@ -275,6 +279,18 @@ class Database extends GrpcServiceObject {
275279
this.requestStream = instance.requestStream as any;
276280
this.pool_.on('error', this.emit.bind(this, 'error'));
277281
this.pool_.open();
282+
this.queryOptions_ = Object.assign(
283+
Object.assign({}, queryOptions),
284+
Database.getEnvironmentQueryOptions()
285+
);
286+
}
287+
288+
static getEnvironmentQueryOptions() {
289+
const options = {} as spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions;
290+
if (process.env.SPANNER_OPTIMIZER_VERSION) {
291+
options.optimizerVersion = process.env.SPANNER_OPTIMIZER_VERSION;
292+
}
293+
return options;
278294
}
279295

280296
batchCreateSessions(
@@ -1219,7 +1235,7 @@ class Database extends GrpcServiceObject {
12191235
return;
12201236
}
12211237

1222-
const snapshot = session!.snapshot(options);
1238+
const snapshot = session!.snapshot(options, this.queryOptions_);
12231239

12241240
snapshot.begin(err => {
12251241
if (err) {
@@ -1737,7 +1753,7 @@ class Database extends GrpcServiceObject {
17371753
return;
17381754
}
17391755

1740-
const snapshot = session!.snapshot(options);
1756+
const snapshot = session!.snapshot(options, this.queryOptions_);
17411757

17421758
this._releaseOnEnd(session!, snapshot);
17431759

0 commit comments

Comments
 (0)