Skip to content

Commit

Permalink
docs: add sequelize sample
Browse files Browse the repository at this point in the history
  • Loading branch information
olavloite committed May 16, 2024
1 parent 96b06b2 commit e6628ba
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ PGAdapter can be used with the following frameworks and tools:
carefully for how to set up ActiveRecord to work with PGAdapter.
1. `Knex.js` query builder can be used with PGAdapter. See [Knex.js sample application](samples/nodejs/knex)
for a sample application.
1. `Sequelize.js` ORM can be used with PGAdapter. See [Sequelize.js sample application](samples/nodejs/sequelize)
for a sample application.

## FAQ
See [Frequently Asked Questions](docs/faq.md) for answers to frequently asked questions.
Expand Down
1 change: 1 addition & 0 deletions samples/nodejs/sequelize/models/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export function initModels(sequelize: Sequelize) {
lastName: {
type: DataTypes.STRING,
},
// The fullName property is generated by Spanner
fullName: {
type: DataTypes.STRING,
},
Expand Down
68 changes: 67 additions & 1 deletion samples/nodejs/sequelize/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import {createDataModel, startPGAdapter} from './init'
import {Album, Concert, initModels, Singer, TicketSale, Track, Venue} from '../models/models';
import {Sequelize} from "sequelize";
import {QueryTypes, Sequelize} from "sequelize";
import {randomInt} from "crypto";
import {randomAlbumTitle, randomFirstName, randomLastName, randomTrackTitle} from "./random";

Expand All @@ -24,6 +24,7 @@ async function main() {
// The emulator runs in the same Docker container as PGAdapter.
const pgAdapter = await startPGAdapter();

console.log('Initializing Sequelize');
// Connect Sequelize to PGAdapter using the standard PostgreSQL Sequelize provider.
const sequelize = new Sequelize('sample-database', null, null, {
dialect: "postgres",
Expand All @@ -32,6 +33,7 @@ async function main() {
// in the test container.
host: 'localhost',
port: pgAdapter.getMappedPort(5432),
ssl: false,

// Setting the timezone is required, as Sequelize otherwise tries to use an INTERVAL to set
// the timezone. That is not supported on PGAdapter, and you will get the following error:
Expand Down Expand Up @@ -59,7 +61,12 @@ async function main() {
await createRandomSingersAndAlbums(sequelize, 20);
await printSingersAlbums();

// Create Venues and Concerts rows.
// The "venues" table contains a JSONB column.
// The "ticket_sales" table contains a text array column.
await createVenuesAndConcerts(sequelize);

await staleRead(sequelize);

// Close the sequelize connection pool and shut down PGAdapter.
await sequelize.close();
Expand Down Expand Up @@ -123,6 +130,7 @@ async function createRandomSingersAndAlbums(sequelize: Sequelize, numSingers: nu
console.log("Finished creating singers and albums");
}

// Selects all Singers and Albums in the database.
async function printSingersAlbums() {
const singers = await Singer.findAll();
for (const singer of singers) {
Expand All @@ -134,6 +142,63 @@ async function printSingersAlbums() {
}
}

// Shows how to execute a stale read on Spanner.
async function staleRead(sequelize: Sequelize) {
console.log("");
console.log("Executing a stale read");

// First get the current timestamp on the server, so we know at which time we had the original
// number of concerts.
const currentTimestamp = (await sequelize.query("SELECT current_timestamp", {
plain: true,
raw: true,
type: QueryTypes.SELECT
}))["current_timestamp"];

// Insert a new concert.
await Concert.create({
name: 'New Concert',
SingerId: (await Singer.findOne({limit: 1})).id,
VenueId: (await Venue.findOne({limit: 1})).id,
startTime: new Date('2023-02-01T20:00:00-05:00'),
endTime: new Date('2023-02-02T02:00:00-05:00'),
});

// Execute a query at a timestamp before we inserted a new concert.
// To do this, we need to get a connection from the pool, as we need to set the read timestamp
// that we want to use for the query on the connection.
const connection = await sequelize.connectionManager.getConnection({type: "read"});
try {
// Set the read timestamp to use.
await sequelize.query(`set spanner.read_only_staleness='read_timestamp ${currentTimestamp.toISOString()}'`,
{raw: true, type: QueryTypes.RAW});

// Fetch all concerts at the timestamp before the insert.
const allConcertsBeforeInsert = await Concert.findAll();
// Verify that the list of concerts does not contain the new concert.
console.log(`Found ${allConcertsBeforeInsert.length} concerts before the insert at timestamp ${currentTimestamp.toISOString()}`);

// Reset the read timestamp to using strong reads and verify that we now see the new concert.
await sequelize.query(`set spanner.read_only_staleness='strong'`,
{raw: true, type: QueryTypes.RAW});
// Fetch all concerts as of now.
const allConcertsAfterInsert = await Concert.findAll();
// We can get the timestamp that was used for the last read from the connection by executing
// 'show spanner.read_timestamp'.
const readTimestamp = (await sequelize.query("show spanner.read_timestamp", {
plain: true,
raw: true,
type: QueryTypes.SELECT
}))["SPANNER.READ_TIMESTAMP"];

// Verify that the list of concerts now contains the new concert.
console.log(`Found ${allConcertsAfterInsert.length} concerts after the insert at timestamp ${readTimestamp.toISOString()}`);
} finally {
// Return the connection to the pool.
sequelize.connectionManager.releaseConnection(connection);
}
}

async function createVenuesAndConcerts(sequelize: Sequelize) {
console.log("Creating venues and concerts...");
await sequelize.transaction(async tx => {
Expand All @@ -156,6 +221,7 @@ async function createVenuesAndConcerts(sequelize: Sequelize) {
endTime: new Date('2023-02-02T02:00:00-05:00'),
}, {transaction: tx});

// The "ticket_sales" table contains an array column "seats".
await TicketSale.create({
ConcertId: concert.id,
customerName: `${randomFirstName()} ${randomLastName()}`,
Expand Down
6 changes: 3 additions & 3 deletions samples/nodejs/sequelize/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export async function createDataModel(sequelize: Sequelize) {
}
console.log("Creating tables");
// Create the data model.
await sequelize.query(`
await sequelize.query(
`
create sequence if not exists singers_seq bit_reversed_positive;
create table "Singers" (
id bigint not null primary key default nextval('singers_seq'),
Expand Down Expand Up @@ -106,8 +107,7 @@ export async function createDataModel(sequelize: Sequelize) {
"createdAt" timestamptz,
"updatedAt" timestamptz,
constraint fk_ticket_sales_concerts foreign key ("ConcertId") references "Concerts" (id)
);
`,
);`,
{type: QueryTypes.RAW})
}

Expand Down

0 comments on commit e6628ba

Please sign in to comment.