Skip to content

Instantly share code, notes, and snippets.

@acburdine
Created March 28, 2025 12:47
Show Gist options
  • Select an option

  • Save acburdine/46dceb9512b5771138dad03e8804013c to your computer and use it in GitHub Desktop.

Select an option

Save acburdine/46dceb9512b5771138dad03e8804013c to your computer and use it in GitHub Desktop.
Spanner Emulator Proto Bundle Issue Reproduction

Instructions to reproduce spanner issue GoogleCloudPlatform/cloud-spanner-emulator#217:

  1. spin up local emulator with docker-compose or similar: docker compose up -d
  2. compile proto enums with protoc: protoc --include_imports --descriptor_set_out=./out.pb enums.proto
  3. Run the following node script: SPANNER_EMULATOR_HOST=localhost:9010 node ./test.js

Notice that if you comment line 49 of test.js out and un-comment line 51, the script succeeds

services:
spanner:
image: "gcr.io/cloud-spanner-emulator/emulator:latest"
pull_policy: always
container_name: spanner-emulator
ports:
- "9010:9010"
- "9020:9020"
syntax = "proto3";
package my.test.proto;
enum TestEnumOne {
A = 0;
B = 1;
C = 2;
}
enum TestEnumTwo {
D = 0;
E = 1;
F = 2;
}
import { Spanner } from "@google-cloud/spanner";
import { randomUUID } from "node:crypto";
import { readFileSync } from "node:fs";
const randomId = () => Math.floor(Math.random() * 10000).toString();
const client = new Spanner({
projectId: process.env.GCP_PROJECT_ID || "test-project",
});
const isEmulator = Boolean(process.env.SPANNER_EMULATOR_HOST);
const instanceId = process.env.GCP_INSTANCE_ID || `instance-${randomId()}`;
const databaseId = process.env.GCP_DATABASE_ID || `database-${randomId()}`;
if (isEmulator) {
console.log(`creating test instance ${instanceId}...`);
await client.createInstance(instanceId, {
config: "emulator-config",
description: "test instance",
nodes: 1,
});
console.log(`creating test database ${databaseId}...`);
await client.instance(instanceId).createDatabase(databaseId);
}
const db = client.instance(instanceId).database(databaseId);
console.log("creating schema");
const protoDescriptors = readFileSync("./out.pb");
const [schemaOp] = await db.updateSchema({
statements: [
"CREATE PROTO BUNDLE (`my.test.proto.TestEnumOne`)",
`CREATE TABLE IF NOT EXISTS \`reproduce_issue\` (
\`id\` STRING(36) NOT NULL DEFAULT (GENERATE_UUID()),
\`str\` STRING(MAX) NOT NULL,
\`proto_field\` \`my.test.proto.TestEnumOne\` NOT NULL DEFAULT ("A")
) PRIMARY KEY (\`id\`)
`,
"CREATE TABLE `table_two` (`id` STRING(36) NOT NULL DEFAULT (GENERATE_UUID())) PRIMARY KEY (`id`)",
],
protoDescriptors,
});
await schemaOp.promise();
const [schemaOp2] = await db.updateSchema({
statements: [
// this statement causes the queries to throw an "unexpected RPC error"
"ALTER PROTO BUNDLE INSERT (`my.test.proto.TestEnumTwo`)",
// this statement works
// "ALTER PROTO BUNDLE INSERT (`my.test.proto.TestEnumTwo`) UPDATE (`my.test.proto.TestEnumOne`)",
'ALTER TABLE `table_two` ADD COLUMN `test_enum` `my.test.proto.TestEnumTwo` NOT NULL DEFAULT ("D")',
],
protoDescriptors,
});
await schemaOp2.promise();
const id = randomUUID();
await db.runTransactionAsync(async (tx) => {
tx.insert("reproduce_issue", {
id,
str: "test string",
});
await tx.commit();
});
const [row] = await db.run({
sql: "SELECT * FROM reproduce_issue WHERE id = @id AND proto_field = 2",
params: { id },
json: true,
});
console.dir(row);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment