-
Notifications
You must be signed in to change notification settings - Fork 2k
Add Spanner stale read sample. #475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -163,6 +163,50 @@ function readData (instanceId, databaseId) { | |
// [END read_data] | ||
} | ||
|
||
function readStaleData (instanceId, databaseId) { | ||
// [START read_stale_data] | ||
// Imports the Google Cloud client library | ||
const Spanner = require('@google-cloud/spanner'); | ||
|
||
// Instantiates a client | ||
const spanner = Spanner(); | ||
|
||
// Uncomment these lines to specify the instance and database to use | ||
// const instanceId = 'my-instance'; | ||
// const databaseId = 'my-database'; | ||
|
||
// Gets a reference to a Cloud Spanner instance and database | ||
const instance = spanner.instance(instanceId); | ||
const database = instance.database(databaseId); | ||
|
||
// Read rows from the Albums table | ||
const albumsTable = database.table('Albums'); | ||
|
||
const query = { | ||
columns: ['SingerId', 'AlbumId', 'AlbumTitle', 'MarketingBudget'], | ||
keySet: { | ||
all: true | ||
} | ||
}; | ||
|
||
const options = { | ||
// Guarantees that all writes that have committed more than 10 seconds ago | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
// are visible | ||
exactStaleness: 10 | ||
}; | ||
|
||
albumsTable.read(query, options) | ||
.then((results) => { | ||
const rows = results[0]; | ||
|
||
rows.forEach((row) => { | ||
const json = row.toJSON(); | ||
console.log(`SingerId: ${json.SingerId.value}, AlbumId: ${json.AlbumId.value}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${json.MarketingBudget ? json.MarketingBudget.value : ''}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we split these into separate lines? This is a lot to read at once... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
}); | ||
}); | ||
// [END read_stale_data] | ||
} | ||
|
||
const cli = require(`yargs`) | ||
.demand(1) | ||
.command( | ||
|
@@ -189,10 +233,17 @@ const cli = require(`yargs`) | |
{}, | ||
(opts) => readData(opts.instanceName, opts.databaseName) | ||
) | ||
.command( | ||
`read-stale <instanceName> <databaseName>`, | ||
`Reads data in an example Cloud Spanner table.`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: mention/explain stale reads in the command description? e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
{}, | ||
(opts) => readStaleData(opts.instanceName, opts.databaseName) | ||
) | ||
.example(`node $0 update "my-instance" "my-database"`) | ||
.example(`node $0 query "my-instance" "my-database"`) | ||
.example(`node $0 insert "my-instance" "my-database"`) | ||
.example(`node $0 read "my-instance" "my-database"`) | ||
.example(`node $0 read-stale "my-instance" "my-database"`) | ||
.wrap(120) | ||
.recommendCommands() | ||
.epilogue(`For more information, see https://cloud.google.com/spanner/docs`); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,103 +53,139 @@ test.after.always(async (t) => { | |
|
||
// create_database | ||
test.serial(`should create an example database`, async (t) => { | ||
const output = await tools.runAsync(`${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}"`, cwd); | ||
t.true(output.includes(`Waiting for operation on ${DATABASE_ID} to complete...`)); | ||
t.true(output.includes(`Created database ${DATABASE_ID} on instance ${INSTANCE_ID}.`)); | ||
const results = await tools.runAsyncWithIO(`${schemaCmd} createDatabase "${INSTANCE_ID}" "${DATABASE_ID}"`, cwd); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the point of this? (Having
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've discover lately that sometimes tests/commands fail silently (return a 0 exit code), but do print some useful info to stderr. This improves test debuggability. |
||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`Waiting for operation on ${DATABASE_ID} to complete...`)); | ||
t.regex(output, new RegExp(`Created database ${DATABASE_ID} on instance ${INSTANCE_ID}.`)); | ||
}); | ||
|
||
// insert_data | ||
test.serial(`should insert rows into an example table`, async (t) => { | ||
let output = await tools.runAsync(`${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`Inserted data.`)); | ||
const results = await tools.runAsyncWithIO(`${crudCmd} insert ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`Inserted data.`)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
}); | ||
|
||
// query_data | ||
test.serial(`should query an example table and return matching rows`, async (t) => { | ||
const output = await tools.runAsync(`${crudCmd} query ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
const results = await tools.runAsyncWithIO(`${crudCmd} query ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
}); | ||
|
||
// read_data | ||
test.serial(`should read an example table`, async (t) => { | ||
const output = await tools.runAsync(`${crudCmd} read ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
const results = await tools.runAsyncWithIO(`${crudCmd} read ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
}); | ||
|
||
// add_column | ||
test.serial(`should add a column to a table`, async (t) => { | ||
const output = await tools.runAsync(`${schemaCmd} addColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`Waiting for operation to complete...`)); | ||
t.true(output.includes(`Added the MarketingBudget column.`)); | ||
const results = await tools.runAsyncWithIO(`${schemaCmd} addColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`Waiting for operation to complete...`)); | ||
t.regex(output, new RegExp(`Added the MarketingBudget column.`)); | ||
}); | ||
|
||
// update_data | ||
test.serial(`should update existing rows in an example table`, async (t) => { | ||
let output = await tools.runAsync(`${crudCmd} update ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`Updated data.`)); | ||
const results = await tools.runAsyncWithIO(`${crudCmd} update ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`Updated data.`)); | ||
}); | ||
|
||
// read_stale_data | ||
test.serial(`should read stale data from an example table`, (t) => { | ||
t.plan(2); | ||
// read-stale-data reads data that is exactly 10 seconds old. So, make sure | ||
// 10 seconds have elapsed since the update_data test. | ||
return new Promise((resolve, reject) => { | ||
setTimeout(async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: can you promisify the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
const results = await tools.runAsyncWithIO(`${crudCmd} read-stale ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
try { | ||
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget: 100000`)); | ||
t.regex(output, new RegExp(`SingerId: 2, AlbumId: 2, AlbumTitle: Forever Hold your Peace, MarketingBudget: 500000`)); | ||
resolve(); | ||
} catch (err) { | ||
reject(err); | ||
} | ||
}, 11000); | ||
}); | ||
}); | ||
|
||
// query_data_with_new_column | ||
test.serial(`should query an example table with an additional column and return matching rows`, async (t) => { | ||
const output = await tools.runAsync(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`SingerId: 1, AlbumId: 1, MarketingBudget: 100000`)); | ||
t.true(output.includes(`SingerId: 2, AlbumId: 2, MarketingBudget: 500000`)); | ||
const results = await tools.runAsyncWithIO(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, MarketingBudget: 100000`)); | ||
t.regex(output, new RegExp(`SingerId: 2, AlbumId: 2, MarketingBudget: 500000`)); | ||
}); | ||
|
||
// create_index | ||
test.serial(`should create an index in an example table`, async (t) => { | ||
let output = await tools.runAsync(`${indexingCmd} createIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`Waiting for operation to complete...`)); | ||
t.true(output.includes(`Added the AlbumsByAlbumTitle index.`)); | ||
const results = await tools.runAsyncWithIO(`${indexingCmd} createIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`Waiting for operation to complete...`)); | ||
t.regex(output, new RegExp(`Added the AlbumsByAlbumTitle index.`)); | ||
}); | ||
|
||
// create_storing_index | ||
test.serial(`should create a storing index in an example table`, async (t) => { | ||
const output = await tools.runAsync(`${indexingCmd} createStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`Waiting for operation to complete...`)); | ||
t.true(output.includes(`Added the AlbumsByAlbumTitle2 index.`)); | ||
const results = await tools.runAsyncWithIO(`${indexingCmd} createStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`Waiting for operation to complete...`)); | ||
t.regex(output, new RegExp(`Added the AlbumsByAlbumTitle2 index.`)); | ||
}); | ||
|
||
// query_data_with_index | ||
test.serial(`should query an example table with an index and return matching rows`, async (t) => { | ||
const output = await tools.runAsync(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`)); | ||
const results = await tools.runAsyncWithIO(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`)); | ||
t.false(output.includes(`AlbumId: 2, AlbumTitle: Total Junk, MarketingBudget:`)); | ||
}); | ||
|
||
test.serial(`should respect query boundaries when querying an example table with an index`, async (t) => { | ||
const output = await tools.runAsync(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID} -s Ardvark -e Zoo`, cwd); | ||
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`)); | ||
t.true(output.includes(`AlbumId: 2, AlbumTitle: Total Junk, MarketingBudget:`)); | ||
const results = await tools.runAsyncWithIO(`${indexingCmd} queryIndex ${INSTANCE_ID} ${DATABASE_ID} -s Ardvark -e Zoo`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go, MarketingBudget:`)); | ||
t.regex(output, new RegExp(`AlbumId: 2, AlbumTitle: Total Junk, MarketingBudget:`)); | ||
}); | ||
|
||
// read_data_with_index | ||
test.serial(`should read an example table with an index`, async (t) => { | ||
const output = await tools.runAsync(`${indexingCmd} readIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
const results = await tools.runAsyncWithIO(`${indexingCmd} readIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
}); | ||
|
||
// read_data_with_storing_index | ||
test.serial(`should read an example table with a storing index`, async (t) => { | ||
const output = await tools.runAsync(`${indexingCmd} readStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
const results = await tools.runAsyncWithIO(`${indexingCmd} readStoringIndex ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
}); | ||
|
||
// read_only_transaction | ||
test.serial(`should read an example table using transactions`, async (t) => { | ||
const output = await tools.runAsync(`${transactionCmd} readOnly ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
t.true(output.includes(`Successfully executed read-only transaction.`)); | ||
const results = await tools.runAsyncWithIO(`${transactionCmd} readOnly ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
const output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, AlbumTitle: Go, Go, Go`)); | ||
t.regex(output, new RegExp(`Successfully executed read-only transaction.`)); | ||
}); | ||
|
||
// read_write_transaction | ||
test.serial(`should read from and write to an example table using transactions`, async (t) => { | ||
let output = await tools.runAsync(`${transactionCmd} readWrite ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`The first album's marketing budget: 100000`)); | ||
t.true(output.includes(`The second album's marketing budget: 500000`)); | ||
t.true(output.includes(`Successfully executed read-write transaction to transfer 200000 from Album 2 to Album 1.`)); | ||
|
||
output = await tools.runAsync(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
t.true(output.includes(`SingerId: 1, AlbumId: 1, MarketingBudget: 300000`)); | ||
t.true(output.includes(`SingerId: 2, AlbumId: 2, MarketingBudget: 300000`)); | ||
let results = await tools.runAsyncWithIO(`${transactionCmd} readWrite ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
let output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`The first album's marketing budget: 100000`)); | ||
t.regex(output, new RegExp(`The second album's marketing budget: 500000`)); | ||
t.regex(output, new RegExp(`Successfully executed read-write transaction to transfer 200000 from Album 2 to Album 1.`)); | ||
|
||
results = await tools.runAsyncWithIO(`${schemaCmd} queryNewColumn ${INSTANCE_ID} ${DATABASE_ID}`, cwd); | ||
output = results.stdout + results.stderr; | ||
t.regex(output, new RegExp(`SingerId: 1, AlbumId: 1, MarketingBudget: 300000`)); | ||
t.regex(output, new RegExp(`SingerId: 2, AlbumId: 2, MarketingBudget: 300000`)); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: inconsistent comment tense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done