Skip to content

Commit d5abeb3

Browse files
committed
Add Spanner stale read sample.
1 parent 6fca8cf commit d5abeb3

File tree

4 files changed

+139
-75
lines changed

4 files changed

+139
-75
lines changed

spanner/README.md

+7-30
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<img src="https://avatars2.githubusercontent.com/u/2810941?v=3&s=96" alt="Google Cloud Platform logo" title="Google Cloud Platform" align="right" height="96" width="96"/>
22

3-
# Google Cloud Spanner Node.js Samples
3+
# Cloud Spanner: Node.js Samples
44

5-
[![Build](https://storage.googleapis.com/cloud-docs-samples-badges/GoogleCloudPlatform/nodejs-docs-samples/nodejs-docs-samples-spanner.svg)]()
5+
[![Build](https://storage.googleapis.com/.svg)]()
66

77
[Cloud Spanner](https://cloud.google.com/spanner/docs/) is a fully managed, mission-critical, relational database service that offers transactional consistency at global scale, schemas, SQL (ANSI 2011 with extensions), and automatic, synchronous replication for high availability.
88

@@ -18,19 +18,6 @@
1818

1919
## Setup
2020

21-
1. Read [Prerequisites][prereq] and [How to run a sample][run] first.
22-
1. Install dependencies:
23-
24-
With **npm**:
25-
26-
npm install
27-
28-
With **yarn**:
29-
30-
yarn install
31-
32-
[prereq]: ../README.md#prerequisites
33-
[run]: ../README.md#how-to-run-a-sample
3421

3522
## Samples
3623

@@ -70,10 +57,11 @@ __Usage:__ `node crud.js --help`
7057

7158
```
7259
Commands:
73-
update <instanceName> <databaseName> Modifies existing rows of data in an example Cloud Spanner table.
74-
query <instanceName> <databaseName> Executes a read-only SQL query against an example Cloud Spanner table.
75-
insert <instanceName> <databaseName> Inserts new rows of data into an example Cloud Spanner table.
76-
read <instanceName> <databaseName> Reads data in an example Cloud Spanner table.
60+
update <instanceName> <databaseName> Modifies existing rows of data in an example Cloud Spanner table.
61+
query <instanceName> <databaseName> Executes a read-only SQL query against an example Cloud Spanner table.
62+
insert <instanceName> <databaseName> Inserts new rows of data into an example Cloud Spanner table.
63+
read <instanceName> <databaseName> Reads data in an example Cloud Spanner table.
64+
read-stale <instanceName> <databaseName> Reads data in an example Cloud Spanner table.
7765
7866
Options:
7967
--help Show help [boolean]
@@ -151,14 +139,3 @@ For more information, see https://cloud.google.com/spanner/docs
151139

152140
## Running the tests
153141

154-
1. Set the **GCLOUD_PROJECT** and **GOOGLE_APPLICATION_CREDENTIALS** environment variables.
155-
156-
1. Run the tests:
157-
158-
With **npm**:
159-
160-
npm test
161-
162-
With **yarn**:
163-
164-
yarn test

spanner/crud.js

+51
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,50 @@ function readData (instanceId, databaseId) {
163163
// [END read_data]
164164
}
165165

166+
function readStaleData (instanceId, databaseId) {
167+
// [START read_stale_data]
168+
// Imports the Google Cloud client library
169+
const Spanner = require('@google-cloud/spanner');
170+
171+
// Instantiates a client
172+
const spanner = Spanner();
173+
174+
// Uncomment these lines to specify the instance and database to use
175+
// const instanceId = 'my-instance';
176+
// const databaseId = 'my-database';
177+
178+
// Gets a reference to a Cloud Spanner instance and database
179+
const instance = spanner.instance(instanceId);
180+
const database = instance.database(databaseId);
181+
182+
// Read rows from the Albums table
183+
const albumsTable = database.table('Albums');
184+
185+
const query = {
186+
columns: ['SingerId', 'AlbumId', 'AlbumTitle', 'MarketingBudget'],
187+
keySet: {
188+
all: true
189+
}
190+
};
191+
192+
const options = {
193+
// Guarantees that all writes that have committed more than 10 seconds ago
194+
// are visible
195+
exactStaleness: 10
196+
};
197+
198+
albumsTable.read(query, options)
199+
.then((results) => {
200+
const rows = results[0];
201+
202+
rows.forEach((row) => {
203+
const json = row.toJSON();
204+
console.log(`SingerId: ${json.SingerId.value}, AlbumId: ${json.AlbumId.value}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${json.MarketingBudget ? json.MarketingBudget.value : ''}`);
205+
});
206+
});
207+
// [END read_stale_data]
208+
}
209+
166210
const cli = require(`yargs`)
167211
.demand(1)
168212
.command(
@@ -189,10 +233,17 @@ const cli = require(`yargs`)
189233
{},
190234
(opts) => readData(opts.instanceName, opts.databaseName)
191235
)
236+
.command(
237+
`read-stale <instanceName> <databaseName>`,
238+
`Reads data in an example Cloud Spanner table.`,
239+
{},
240+
(opts) => readStaleData(opts.instanceName, opts.databaseName)
241+
)
192242
.example(`node $0 update "my-instance" "my-database"`)
193243
.example(`node $0 query "my-instance" "my-database"`)
194244
.example(`node $0 insert "my-instance" "my-database"`)
195245
.example(`node $0 read "my-instance" "my-database"`)
246+
.example(`node $0 read-stale "my-instance" "my-database"`)
196247
.wrap(120)
197248
.recommendCommands()
198249
.epilogue(`For more information, see https://cloud.google.com/spanner/docs`);

spanner/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
},
2323
"devDependencies": {
2424
"@google-cloud/nodejs-repo-tools": "1.4.17",
25-
"ava": "0.21.0",
25+
"ava": "0.22.0",
2626
"proxyquire": "1.8.0",
27-
"sinon": "3.2.0"
27+
"sinon": "3.2.1"
2828
},
2929
"cloud-repo-tools": {
3030
"requiresKeyFile": true,

spanner/system-test/spanner.test.js

+79-43
Original file line numberDiff line numberDiff line change
@@ -53,103 +53,139 @@ test.after.always(async (t) => {
5353

5454
// create_database
5555
test.serial(`should create an example database`, async (t) => {
56-
const output = await tools.runAsync(`${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}"`, cwd);
57-
t.true(output.includes(`Waiting for operation on ${DATABASE_ID} to complete...`));
58-
t.true(output.includes(`Created database ${DATABASE_ID} on instance ${INSTANCE_ID}.`));
56+
const results = await tools.runAsyncWithIO(`${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}"`, cwd);
57+
const output = results.stdout + results.stderr;
58+
t.regex(output, new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`));
59+
t.regex(output, new RegExp(`Created database ${DATABASE_ID} on instance ${INSTANCE_ID}.`));
5960
});
6061

6162
// insert_data
6263
test.serial(`should insert rows into an example table`, async (t) => {
63-
let output = await tools.runAsync(`${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
64-
t.true(output.includes(`Inserted data.`));
64+
const results = await tools.runAsyncWithIO(`${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
65+
const output = results.stdout + results.stderr;
66+
t.regex(output, new RegExp(`Inserted data.`));
6567
});
6668

6769
// query_data
6870
test.serial(`should query an example table and return matching rows`, async (t) => {
69-
const output = await tools.runAsync(`${crudCmd} query ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
70-
t.true(output.includes(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`));
71+
const results = await tools.runAsyncWithIO(`${crudCmd} query ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
72+
const output = results.stdout + results.stderr;
73+
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`));
7174
});
7275

7376
// read_data
7477
test.serial(`should read an example table`, async (t) => {
75-
const output = await tools.runAsync(`${crudCmd} read ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
76-
t.true(output.includes(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`));
78+
const results = await tools.runAsyncWithIO(`${crudCmd} read ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
79+
const output = results.stdout + results.stderr;
80+
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`));
7781
});
7882

7983
// add_column
8084
test.serial(`should add a column to a table`, async (t) => {
81-
const output = await tools.runAsync(`${schemaCmd} addColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
82-
t.true(output.includes(`Waiting for operation to complete...`));
83-
t.true(output.includes(`Added the MarketingBudget column.`));
85+
const results = await tools.runAsyncWithIO(`${schemaCmd} addColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
86+
const output = results.stdout + results.stderr;
87+
t.regex(output, new RegExp(`Waiting for operation to complete...`));
88+
t.regex(output, new RegExp(`Added the MarketingBudget column.`));
8489
});
8590

8691
// update_data
8792
test.serial(`should update existing rows in an example table`, async (t) => {
88-
let output = await tools.runAsync(`${crudCmd} update ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
89-
t.true(output.includes(`Updated data.`));
93+
const results = await tools.runAsyncWithIO(`${crudCmd} update ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
94+
const output = results.stdout + results.stderr;
95+
t.regex(output, new RegExp(`Updated data.`));
96+
});
97+
98+
// read_stale_data
99+
test.serial(`should read stale data from an example table`, (t) => {
100+
t.plan(2);
101+
// read-stale-data reads data that is exactly 10 seconds old. So, make sure
102+
// 10 seconds have elapsed since the update_data test.
103+
return new Promise((resolve, reject) => {
104+
setTimeout(async () => {
105+
const results = await tools.runAsyncWithIO(`${crudCmd} read-stale ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
106+
const output = results.stdout + results.stderr;
107+
try {
108+
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget: 100000`));
109+
t.regex(output, new RegExp(`SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget: 500000`));
110+
resolve();
111+
} catch (err) {
112+
reject(err);
113+
}
114+
}, 11000);
115+
});
90116
});
91117

92118
// query_data_with_new_column
93119
test.serial(`should query an example table with an additional column and return matching rows`, async (t) => {
94-
const output = await tools.runAsync(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
95-
t.true(output.includes(`SingerId: 1, AlbumId: 1, MarketingBudget: 100000`));
96-
t.true(output.includes(`SingerId: 2, AlbumId: 2, MarketingBudget: 500000`));
120+
const results = await tools.runAsyncWithIO(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
121+
const output = results.stdout + results.stderr;
122+
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, MarketingBudget: 100000`));
123+
t.regex(output, new RegExp(`SingerId: 2, AlbumId: 2, MarketingBudget: 500000`));
97124
});
98125

99126
// create_index
100127
test.serial(`should create an index in an example table`, async (t) => {
101-
let output = await tools.runAsync(`${indexingCmd} createIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
102-
t.true(output.includes(`Waiting for operation to complete...`));
103-
t.true(output.includes(`Added the AlbumsByAlbumTitle index.`));
128+
const results = await tools.runAsyncWithIO(`${indexingCmd} createIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
129+
const output = results.stdout + results.stderr;
130+
t.regex(output, new RegExp(`Waiting for operation to complete...`));
131+
t.regex(output, new RegExp(`Added the AlbumsByAlbumTitle index.`));
104132
});
105133

106134
// create_storing_index
107135
test.serial(`should create a storing index in an example table`, async (t) => {
108-
const output = await tools.runAsync(`${indexingCmd} createStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
109-
t.true(output.includes(`Waiting for operation to complete...`));
110-
t.true(output.includes(`Added the AlbumsByAlbumTitle2 index.`));
136+
const results = await tools.runAsyncWithIO(`${indexingCmd} createStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
137+
const output = results.stdout + results.stderr;
138+
t.regex(output, new RegExp(`Waiting for operation to complete...`));
139+
t.regex(output, new RegExp(`Added the AlbumsByAlbumTitle2 index.`));
111140
});
112141

113142
// query_data_with_index
114143
test.serial(`should query an example table with an index and return matching rows`, async (t) => {
115-
const output = await tools.runAsync(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
116-
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`));
144+
const results = await tools.runAsyncWithIO(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
145+
const output = results.stdout + results.stderr;
146+
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`));
117147
t.false(output.includes(`AlbumId: 2, AlbumTitle: Total Junk, MarketingBudget:`));
118148
});
119149

120150
test.serial(`should respect query boundaries when querying an example table with an index`, async (t) => {
121-
const output = await tools.runAsync(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID} -s Ardvark -e Zoo`, cwd);
122-
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`));
123-
t.true(output.includes(`AlbumId: 2, AlbumTitle: Total Junk, MarketingBudget:`));
151+
const results = await tools.runAsyncWithIO(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID} -s Ardvark -e Zoo`, cwd);
152+
const output = results.stdout + results.stderr;
153+
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`));
154+
t.regex(output, new RegExp(`AlbumId: 2, AlbumTitle: Total Junk, MarketingBudget:`));
124155
});
125156

126157
// read_data_with_index
127158
test.serial(`should read an example table with an index`, async (t) => {
128-
const output = await tools.runAsync(`${indexingCmd} readIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
129-
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go`));
159+
const results = await tools.runAsyncWithIO(`${indexingCmd} readIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
160+
const output = results.stdout + results.stderr;
161+
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go`));
130162
});
131163

132164
// read_data_with_storing_index
133165
test.serial(`should read an example table with a storing index`, async (t) => {
134-
const output = await tools.runAsync(`${indexingCmd} readStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
135-
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go`));
166+
const results = await tools.runAsyncWithIO(`${indexingCmd} readStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
167+
const output = results.stdout + results.stderr;
168+
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go`));
136169
});
137170

138171
// read_only_transaction
139172
test.serial(`should read an example table using transactions`, async (t) => {
140-
const output = await tools.runAsync(`${transactionCmd} readOnly ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
141-
t.true(output.includes(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`));
142-
t.true(output.includes(`Successfully executed read-only transaction.`));
173+
const results = await tools.runAsyncWithIO(`${transactionCmd} readOnly ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
174+
const output = results.stdout + results.stderr;
175+
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`));
176+
t.regex(output, new RegExp(`Successfully executed read-only transaction.`));
143177
});
144178

145179
// read_write_transaction
146180
test.serial(`should read from and write to an example table using transactions`, async (t) => {
147-
let output = await tools.runAsync(`${transactionCmd} readWrite ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
148-
t.true(output.includes(`The first album's marketing budget: 100000`));
149-
t.true(output.includes(`The second album's marketing budget: 500000`));
150-
t.true(output.includes(`Successfully executed read-write transaction to transfer 200000 from Album 2 to Album 1.`));
151-
152-
output = await tools.runAsync(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
153-
t.true(output.includes(`SingerId: 1, AlbumId: 1, MarketingBudget: 300000`));
154-
t.true(output.includes(`SingerId: 2, AlbumId: 2, MarketingBudget: 300000`));
181+
let results = await tools.runAsyncWithIO(`${transactionCmd} readWrite ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
182+
let output = results.stdout + results.stderr;
183+
t.regex(output, new RegExp(`The first album's marketing budget: 100000`));
184+
t.regex(output, new RegExp(`The second album's marketing budget: 500000`));
185+
t.regex(output, new RegExp(`Successfully executed read-write transaction to transfer 200000 from Album 2 to Album 1.`));
186+
187+
results = await tools.runAsyncWithIO(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd);
188+
output = results.stdout + results.stderr;
189+
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, MarketingBudget: 300000`));
190+
t.regex(output, new RegExp(`SingerId: 2, AlbumId: 2, MarketingBudget: 300000`));
155191
});

0 commit comments

Comments
 (0)