My personal fork of spanner-cli, interactive command line tool for Cloud Spanner.
spanner-mycli is an interactive command line tool for Google Cloud Spanner.
You can control your Spanner databases with idiomatic SQL commands.
spanner-mycli was forked from spanner-cli v0.10.6 and restarted its version numbering from v0.1.0. There are differences between spanner-mycli and spanner-cli that include not only functionality but also philosophical differences.
- Advanced query plan features for constrained display environments and comprehensive analysis. See docs/query_plan.md for details.
- Configurable
EXPLAIN ANALYZEwith customizable execution stats columns usingCLI_ANALYZE_COLUMNSand inline stats usingCLI_INLINE_STATS - Configurable query plan appendix presets and sections with
EXPLAIN PRINT=<preset-or-sections>andCLI_EXPLAIN_PRINT_SECTIONS - Query plan investigation with
EXPLAIN [ANALYZE] LAST QUERYfor re-rendering without re-execution andSHOW PLAN NODEfor inspecting specific plan nodes - Compact format (
FORMAT=COMPACT) and wrapped plans (WIDTH=<width>) with hanging indent for limited display spaces like narrow terminals, code blocks, and technical documentation - Opt-in
CLI_EXPLAIN_CONCISE_METADATAto omit exactseekable_key_size: 0and automaticscan_methodfrom rendered plan titles (default keeps them) - Optional EXPLAIN Operator header label with
CLI_EXPLAIN_OPERATOR_HEADER(empty keeps the existing WIDTH-dependent name) - Query plan linter (EARLY EXPERIMENTAL) using
CLI_LINT_PLANsystem variable for heuristic query plan analysis - Query profiles (EARLY EXPERIMENTAL) for rendering sampled query plans using
SHOW QUERY PROFILESandSHOW QUERY PROFILE
- Configurable
- Respects my minor use cases
- Protocol Buffers support as
SHOW LOCAL PROTO,SHOW REMOTE PROTO,PULL REMOTE PROTO,SYNC PROTO BUNDLEstatement - Can use embedded runtime backends (
--embedded-emulator,--embedded-omni) - Support query parameters
- Test root-partitionable with
TRY PARTITIONED QUERY <sql>command - Experimental Partitioned Query and Data Boost support.
- GenAI support(
GEMINIstatement). - BigQuery support (
BIGQUERYstatement). - Inspect schema object definitions with
SHOW CREATE <type> <fqn>;. - Interactive DDL batching
- Async DDL execution support (
--asyncflag andDDL_EXECUTION_MODE/DDL_ASYNC_WAIT_TIMEOUTsystem variables) - Experimental Cassandra interface support as
CQL <cql>statement. - Support split points.
- Run as MCP (Model Context Protocol) server (EXPERIMENTAL,
--mcp). See Model Context Protocol for more information.- Statement calls are serialized. Calls cancelled while waiting do not execute; cancellation after execution starts does not guarantee rollback.
- Protocol Buffers support as
- Respects training and verification use-cases.
- gRPC logging(
--log-grpc) - Support mutations
- gRPC logging(
- Respects batch use cases as well as interactive use cases
- Breaking change from spanner-cli: Default output format for batch mode is
TABLE(same as interactive mode), notTAB. Use--format=TABfor tab-separated output.
- Breaking change from spanner-cli: Default output format for batch mode is
- More
gcloud spanner databases execute-sqlcompatibilities- Support compatible flags (
--sql,--query-mode,--strong,--read-timestamp,--timeout)
- Support compatible flags (
- More
gcloud spanner databases ddl updatecompatibilities- Support
--proto-descriptor-fileflag
- Support
- More Google Cloud Spanner CLI (
gcloud alpha spanner cli) compatibilities- Support
--skip-column-namesflag to suppress column headers in output (useful for scripting) - Support
--hostand--portflags as first-class options - Support
--deployment-endpointas an alias for--endpoint - Support
--html,--xml, and--csvoutput format options with proper escaping (security-enhanced compared to reference implementation) - Support
--format=jsonlfor type-aware JSON Lines output: INT64/ENUM as numbers, BOOL as booleans, ARRAY as JSON arrays, STRUCT as JSON objects, NULL as null
- Support
- Generalized concepts to extend without a lot of original syntax
- Generalized system variables concept inspired by Spanner JDBC properties
SET <name> = <value>statementSET LOCAL <name> = <value>statement (transaction-scoped; the value reverts when the transaction ends)RESET ALLstatement (restore resettable variables to their post-flag/--set startup snapshots; init-command values are resettable)RESET <name>statement (restore one canonical name or alias to the same startup snapshot)SHOW VARIABLESstatementSHOW VARIABLE <name>statement--set <name>=<value>flag
- Generalized system variables concept inspired by Spanner JDBC properties
- Improved interactive experience
- Use
hymkor/go-multiline-nyinstead ofchzyer/readline"- Native multi-line editing
- Improved prompt
- Use
%for prompt expansion, instead of\to avoid escaping - Allow newlines in prompt using
%n - System variables expansion
- Prompt2 with margin and waiting status
- Use
- Autowrap and auto adjust column width to fit within terminal width (overridable with
CLI_FIXED_WIDTH) whenCLI_AUTOWRAP = TRUE. Opt-in end truncation withCLI_ELLIPSIS = TRUEon a finite width (TTY orCLI_FIXED_WIDTH); unconstrained output still wraps. Pluggable width strategies viaCLI_WIDTH_STRATEGY(GREEDY_FREQUENCY,PROPORTIONAL,MARGINAL_COST). - Pager support when
CLI_USE_PAGER = TRUE - Progress bar of DDL execution.
- Syntax highlight when
CLI_ENABLE_HIGHLIGHT = TRUE - Fuzzy finder (default
Ctrl+T, configurable viaCLI_FUZZY_FINDER_KEY) powered by fzf for databases, tables, system variables, database roles, operations, query-profile fingerprints, and statement names
- Use
- Utilize other libraries
- Dogfooding
cloudspannerecosystem/memefish- Spin out memefish logic as
apstndb/gsqlutils.
- Spin out memefish logic as
- Utilize
apstndb/spantypeandapstndb/spanvalue
- Dogfooding
Do not use this tool for production databases as the tool is experimental/alpha quality forever.
This software will not have a stable release. In other words, v1.0.0 will never be released. It will be operated as a kind of ZeroVer.
v0.X.Y will be operated as follows:
- The initial release version is v0.1.0, forked from spanner-cli v0.10.6.
- The patch version Y will be incremented for changes that include only bug fixes.
- The minor version X will always be incremented when there are new features or changes related to compatibility.
- As a general rule, unreleased updates to the main branch will be released within one week.
Download pre-built binaries from GitHub Releases.
Install Go and run the following command.
# Requires Go 1.26+
go install github.com/apstndb/spanner-mycli@latest
Release assets include spanner-mycli-slim, which leaves out the optional
GEMINI/LLM, BigQuery, and Cassandra-interface CQL statement families. Use it
when those capabilities are not needed. Omitted statements, --vertexai-*
flags, and related system variables or TOML keys are rejected rather than
ignored. Core MCP remains. Slim is not a sandbox or a smaller module graph;
see Slim binary.
Build the same variant from source with:
CGO_ENABLED=0 go build -o spanner-mycli-slim ./cmd/spanner-mycli-slimFor a reproducible full-versus-slim relative size comparison using identical stripping and target settings, see Slim binary.
Use the container image from GitHub Container Registry.
https://github.com/apstndb/spanner-mycli/pkgs/container/spanner-mycli
Usage: spanner-mycli [flags]
Flags:
-p, --project=STRING (required) GCP Project ID ($SPANNER_PROJECT_ID).
-i, --instance=STRING (required) Cloud Spanner Instance ID ($SPANNER_INSTANCE_ID)
-d, --database=STRING Cloud Spanner Database ID. Optional when --detached is used
($SPANNER_DATABASE_ID).
--detached Start in detached mode, ignoring database env var/flag
-e, --execute=STRING Execute SQL statement and quit. --sql is an alias.
-f, --file=STRING Execute SQL from a local file, process substitution, stdin (-), or a
file/http/https/gs URI and quit. --source is an alias. Remote scripts
run against the selected connection (100 MiB limit; GCS uses configured
credentials).
--init-command=STRING SQL to execute after connecting, before other input. Failure aborts
startup.
--init-command-add=INIT-COMMAND-ADD Additional startup SQL (repeatable). Appended after --init-command.
Failure aborts startup.
-t, --table Display output in table format for batch mode.
--html Display output in HTML format.
--xml Display output in XML format.
--csv Display output in CSV format.
--format=STRING Output format (table, tab, tsv, vertical, html, xml, csv, jsonl)
-v, --verbose Display verbose output.
--credential=STRING Use the specific credential file
--prompt=PROMPT Set the prompt to the specified format (default: "spanner%t> ")
--prompt2=PROMPT2 Set the prompt2 to the specified format (default: "%P%R> ")
--history=HISTORY Set the history file to the specified path (default:
~/.spanner_mycli_history)
--priority=STRING Set default request priority (HIGH|MEDIUM|LOW)
--role=STRING Use the specific database role. --database-role is an alias.
--endpoint=STRING Set the Spanner API endpoint (host:port)
--host=STRING Host on which Spanner server is located
--port=INT Port number for Spanner connection
--directed-read=STRING Directed read option: replica_location[:READ_ONLY|READ_WRITE] shorthand,
or DirectedReadOptions protobuf JSON
--set=KEY=VALUE Set system variables e.g. --set=name1=value1 --set=name2=value2
--param=KEY=VALUE Set query parameters, it can be literal or type(EXPLAIN/DESCRIBE only).
Names are case-insensitive; conflicting case aliases are rejected;
identical aliases collapse to one name. e.g. --param="p1='string_value'"
--param=p2=FLOAT64
--proto-descriptor-file=STRING Local path or file://, http(s)://, or gs:// URI of a FileDescriptorSet
(.pb) or proto source (.proto). file:// aliases the decoded path;
gs:// keeps URI identity. Path extension selects format. 100 MiB.
GCS uses configured credentials.
--insecure Permit plaintext gRPC (no TLS). --skip-tls-verify is an alias. Cannot be
combined with custom CA or client certificates.
--ca-cert-file=STRING PEM CA certificate file used as the TLS trust bundle (replaces system
roots). Requires an explicit --endpoint or --host. Startup-only.
--client-cert-file=STRING PEM client certificate for mTLS. Must be paired with --client-cert-key.
Requires an explicit --endpoint or --host. Startup-only.
--client-cert-key=STRING PEM client private key for mTLS. Must be paired with --client-cert-file.
Never shown by SHOW VARIABLES. Startup-only.
--without-authentication Do not send Google bearer credentials to the Spanner endpoint. Requires
an explicit --endpoint or --host and at least one custom TLS file. Does
not enable plaintext or skip certificate verification. Default: false.
--embedded-emulator Use embedded Cloud Spanner Emulator. --project, --instance, --database,
--endpoint, --insecure will be automatically configured.
--embedded-omni Use embedded experimental Spanner Omni. --project, --instance,
--database, --endpoint, --insecure will be automatically configured.
--emulator-image=STRING container image for embedded runtime (--embedded-emulator or
--embedded-omni)
--emulator-platform=STRING Container platform (e.g. linux/amd64, linux/arm64) for embedded runtime
--sample-database=STRING Initialize embedded runtime with built-in sample (e.g. fingraph,
singers, banking) or path to a metadata file (.json, .yaml, .yml).
Requires --embedded-emulator or --embedded-omni. Cannot be combined with
--detached.
--list-samples List available sample databases and exit
--output-template=STRING Filepath of output template. (EXPERIMENTAL)
--log-level=STRING Set CLI log level (DEBUG, INFO, WARN, ERROR). INFO and DEBUG include
embedded runtime container lifecycle logs. SQL SET CLI_LOG_LEVEL does not
change those container logs.
--log-grpc Show gRPC logs
--spanner-metrics-exporter="off" Opt-in caller-owned Spanner client metrics exporter: off (default,
no provider) or otlp (OTLP HTTP/protobuf). OTEL_* environment variables
alone do not enable export. SQL SET cannot change this.
--spanner-metrics-endpoint=STRING Absolute http/https collector URL for --spanner-metrics-exporter=otlp.
Host required; no userinfo, query, or fragment. Missing/root path becomes
/v1/metrics. Required with otlp; forbidden with off.
--spanner-traces-exporter="off" Opt-in process-owned Spanner client traces exporter: off (default, no
pipeline or global TracerProvider change) or otlp (OTLP HTTP/protobuf).
OTEL_* environment variables alone do not enable export. SQL SET cannot
change this.
--spanner-traces-endpoint=STRING Absolute http/https collector URL for --spanner-traces-exporter=otlp.
Host required; no userinfo, query, or fragment. Missing/root path becomes
/v1/traces. Required with otlp; forbidden with off.
--spanner-traces-sample-ratio=0.01 Root sampling ratio in [0,1] used when --spanner-traces-exporter=otlp.
ParentBased: a sampled parent is honored and an unsampled parent drops
the child. Default 0.01. Ignored when export is off.
--query-mode=QUERY-MODE Mode in which the query must be processed. Allowed values: NORMAL, PLAN,
PROFILE, WITH_STATS, WITH_PLAN_AND_STATS.
--strong Perform a strong query.
--read-timestamp=STRING Perform a query at the given timestamp.
--database-dialect=DATABASE-DIALECT The SQL dialect of the Cloud Spanner Database. Allowed values:
POSTGRESQL, GOOGLE_STANDARD_SQL, DATABASE_DIALECT_UNSPECIFIED. Omit this
flag to leave it unset.
--impersonate-service-account=STRING Impersonate service account email
-h, --help Show this help message and exit.
--version Show version string.
--enable-partitioned-dml Partitioned DML as default (AUTOCOMMIT_DML_MODE=PARTITIONED_NON_ATOMIC)
--timeout=STRING Statement timeout (e.g., '10s', '5m', '1h'). Omit for 10m on ordinary
statements and 24h on partitioned DML.
--idle-transaction-timeout=STRING User-idle transaction timeout (e.g., '60s', '5m'). Omit or empty leaves
CLI_IDLE_TRANSACTION_TIMEOUT disabled (NULL).
--async Return immediately, without waiting for the operation in progress to
complete
--try-partition-query Test whether the query can be executed as partition query without
execution
--mcp Run as MCP server
--skip-system-command Do not allow system commands
--system-command=ON|OFF Enable or disable system commands (ON/OFF). Default: ON.
--tee=STRING Append a copy of output to the specified file (both screen and file)
-o, --output=STRING Redirect query/data output to file (overwrites existing file)
--skip-column-names Suppress column headers in output
--table-streaming="AUTO" Table streaming output mode: AUTO/FALSE buffer table output, TRUE streams
table output. Non-table formats always stream.
--color="AUTO" ANSI styling in output: AUTO (styled if TTY), TRUE (always styled),
FALSE (never styled)
-q, --quiet Suppress result lines like 'rows in set' for clean output
--vertexai-project=STRING Gemini Enterprise project override
--vertexai-model=VERTEXAI-MODEL Gemini model (default: gemini-3.7-flash)
--vertexai-location=VERTEXAI-LOCATION Gemini Enterprise location (default: global)
Unless you specify a credential file with --credential, this tool uses Application Default Credentials as credential source to connect to Spanner databases.
Please make sure to prepare your credential by gcloud auth application-default login.
If you're running spanner-mycli in docker container on your local machine, you have to pass local credentials to the container with the following command.
docker run -it \
-e GOOGLE_APPLICATION_CREDENTIALS=/tmp/credentials.json \
-v $HOME/.config/gcloud/application_default_credentials.json:/tmp/credentials.json:ro \
spanner-mycli --help
$ docker run -it \
-v $HOME/.config/gcloud/application_default_credentials.json:/home/nonroot/.config/gcloud/application_default_credentials.json:ro \
ghcr.io/apstndb/spanner-mycli --help
Rejected input remains in the editor for correction, including syntax errors and
multiple statements submitted together. Press Ctrl+C to discard the draft.
Interactive input accepts one statement at a time; use \. file.sql or
--file file.sql to execute a script containing multiple statements.
$ spanner-mycli -p myproject -i myinstance -d mydb
Connected: project="myproject", instance="myinstance", database="mydb", role="(default)", endpoint="(client default)"
spanner> CREATE TABLE users (
-> id INT64 NOT NULL,
-> name STRING(16) NOT NULL,
-> active BOOL NOT NULL
-> ) PRIMARY KEY (id);
Query OK, 0 rows affected (30.60 sec)
spanner> SHOW TABLES;
+----------------+
| Tables_in_mydb |
+----------------+
| users |
+----------------+
1 rows in set (18.66 msecs)
spanner> INSERT INTO users (id, name, active) VALUES (1, "foo", true), (2, "bar", false);
Query OK, 2 rows affected (5.08 sec)
spanner> SELECT * FROM users ORDER BY id ASC;
+----+------+--------+
| id | name | active |
+----+------+--------+
| 1 | foo | true |
| 2 | bar | false |
+----+------+--------+
2 rows in set (3.09 msecs)
spanner> BEGIN;
Query OK, 0 rows affected (0.02 sec)
spanner(rw txn)> DELETE FROM users WHERE active = false;
Query OK, 1 rows affected (0.61 sec)
spanner(rw txn)> COMMIT;
Query OK, 0 rows affected (0.20 sec)
spanner> SELECT * FROM users ORDER BY id ASC;
+----+------+--------+
| id | name | active |
+----+------+--------+
| 1 | foo | true |
+----+------+--------+
1 rows in set (2.58 msecs)
spanner> DROP TABLE users;
Query OK, 0 rows affected (25.20 sec)
spanner> SHOW TABLES;
Empty set (2.02 msecs)
spanner> EXIT;
Bye
By passing SQL from standard input, spanner-mycli runs in batch mode.
$ echo 'SELECT * FROM users;' | spanner-mycli -p myproject -i myinstance -d mydb
+----+------+--------+
| id | name | active |
+----+------+--------+
| 1 | foo | true |
| 2 | bar | false |
+----+------+--------+
You can also pass SQL with command line option -e.
$ spanner-mycli -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;'
+----+------+--------+
| id | name | active |
+----+------+--------+
| 1 | foo | true |
| 2 | bar | false |
+----+------+--------+
For tab-separated output (useful for scripting), use --format=TAB:
$ spanner-mycli -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;' --format=TAB
id name active
1 foo true
2 bar false
TAB writes values as-is, so values containing tabs or newlines break the row/column structure.
Use --format=TSV for the same layout with delimiter escaping: tab, newline, carriage return, and
backslash inside values are escaped as \t, \n, \r, and \\, guaranteeing one row per line
and one field per tab-separated column. SQL NULL and the STRING NULL remain
indistinguishable in TAB/TSV; see format contracts.
With --skip-column-names option, column headers are suppressed in output (useful for scripting).
$ spanner-mycli -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;' --format=TAB --skip-column-names
1 foo true
2 bar false
# With table format
$ spanner-mycli -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;' -t --skip-column-names
+---+-----+-------+
| 1 | foo | true |
| 2 | bar | false |
+---+-----+-------+
The --timeout flag allows you to set a timeout for SQL statement execution, compatible with gcloud spanner databases execute-sql behavior.
# Set 30 second timeout for queries
$ spanner-mycli --timeout 30s -p myproject -i myinstance -d mydb -e 'SELECT * FROM large_table;'
# Set 5 minute timeout for partitioned DML
$ spanner-mycli --timeout 5m --enable-partitioned-dml -p myproject -i myinstance -d mydb -e 'UPDATE large_table SET status = "active";'
# Omit --timeout, and any timeout config / --set, to leave STATEMENT_TIMEOUT stored as NULL.
# Execution then uses 10 minutes for ordinary statements and 24 hours for partitioned DML.
$ spanner-mycli -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;'--idle-transaction-timeout sets CLI_IDLE_TRANSACTION_TIMEOUT using the same
duration strings (60s, 5m). Omit the flag, and any matching config / --set,
to leave idle expiry stored as NULL (disabled).
You can also configure the stored statement timeout interactively using
STATEMENT_TIMEOUT. NULL is the default when --timeout, the timeout config
key, and --set STATEMENT_TIMEOUT all omit a value; 10m / 24h are
execution fallbacks, not the stored value. See
docs/system_variables.md.
spanner> SET STATEMENT_TIMEOUT = '2m';
Query OK, 0 rows affected (0.00 sec)
spanner> SHOW VARIABLE STATEMENT_TIMEOUT;
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| STATEMENT_TIMEOUT | 2m0s |
+-------------------+-------+
1 rows in set (0.00 sec)spanner-mycli provides two ways to capture output to files:
- Tee functionality: Append output to a file while still displaying it on the console (like the Unix
teecommand) - Output redirect: Send query and result output to a file while prompts, progress, and errors stay on their normal screen streams
Both features are available through command-line options and interactive meta-commands.
# Log all query results to a file while displaying on screen
$ spanner-mycli --tee output.log -p myproject -i myinstance -d mydb
# In batch mode with --tee
$ spanner-mycli --tee queries.log -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;'spanner> \T session.log -- Start tee to session.log (both screen and file)
spanner> SELECT * FROM users; -- This query and result will be logged and displayed
spanner> \t -- Stop tee
spanner> SELECT * FROM keys; -- This won't be logged (screen only)
spanner> \T another.log -- Start tee to a different file# Redirect query/data output to file (overwrites existing file)
$ spanner-mycli --output backup.sql -p myproject -i myinstance -d mydb -e 'DUMP DATABASE;'
# Useful for clean SQL exports while leaving progress and errors on screen
$ spanner-mycli --output export.sql -p myproject -i myinstance -d mydbspanner> \o backup.sql -- Redirect query/data output to file (overwrite if it exists)
spanner> DUMP DATABASE; -- SQL goes to file, progress shows on screen
spanner> \o -- Disable redirect (return to screen output)
spanner> SELECT * FROM users; -- This shows on screen only
-- Alternative using \O (symmetric with \T/\t pattern)
spanner> \o export.sql -- Redirect query/data output to file (overwrite if it exists)
spanner> SELECT * FROM keys; -- Output goes to file only
spanner> \O -- Disable redirect using \ODUMP SCHEMA and DUMP DATABASE move inline foreign keys that reference
tables created later into ALTER TABLE ... ADD statements after the other DDL,
before any data. This allows an empty cyclic foreign-key schema to be restored
even when Spanner returns its constraints inside CREATE TABLE. If an affected
table definition cannot be parsed or safely rewritten, the dump fails before
emitting SQL. DUMP TABLES does not export or modify DDL. Populated cycles
involving enforced foreign keys or interleave relationships are rejected by
default. The opt-in CLI_DUMP_CYCLIC_MODE = 'MUTATE' mode emits one mutation
transaction per populated cyclic table group; it does not make the whole restore
atomic or predict service commit limits. See cyclic DUMP restoration
for usage, local buffering limits, and target prerequisites.
The tee file will contain:
- Query results and output
- SQL statements when
CLI_ECHO_INPUTis enabled - Error messages and warnings
- Result metadata (row counts, execution times)
The tee file will NOT contain:
- Interactive prompts (e.g.,
spanner>) - Progress indicators (e.g., DDL progress bars)
- Confirmation dialogs (e.g., DROP DATABASE confirmations)
- Readline input display
- Dynamic control with meta-commands: Start and stop logging during the session
- File switching: Starting a new tee (with
\T) automatically closes the previous file - Quoted filenames: Supports filenames with spaces:
\T "my output.log" - Combine with --tee: Start with
--tee, use\tto pause, and\Tto resume
- Tee files use append mode;
--outputand\ooverwrite existing content - Files are created if they don't exist
- Only regular files are supported (not directories, FIFOs, or device files)
- Tee file write failures warn once and leave console output running
- File-only output preserves write errors instead of treating the failed file as optional. A failed export can leave a partial file; do not replay it as a complete dump.
- Result-display failures, including summaries and query-plan appendices, report an error. This does not undo a statement that already completed successfully.
# Example: Logging a session with CLI_ECHO_INPUT
$ spanner-mycli --tee session.log -p myproject -i myinstance -d mydb
Connected: project="myproject", instance="myinstance", database="mydb", role="(default)", endpoint="(client default)"
spanner> SET CLI_ECHO_INPUT = TRUE;
Query OK, 0 rows affected (0.00 sec)
spanner> SELECT 1 AS test;
# In session.log:
# SELECT 1 AS test;
# +------+
# | test |
# +------+
# | 1 |
# +------+
# 1 rows in set (2.41 msecs)EXPLAIN shows the query plan without executing the query:
EXPLAIN SELECT SingerId, FirstName FROM Singers WHERE FirstName LIKE "A%";The Cloud Spanner Emulator does not return query plans, so EXPLAIN and
EXPLAIN ANALYZE report an error there. See query plan examples and settings
for complete output, execution-method labels, and display options.
EXPLAIN ANALYZE executes the query and shows its plan and execution profile:
EXPLAIN ANALYZE SELECT SingerId, FirstName FROM Singers WHERE FirstName LIKE "A%";See execution profile examples and query plan variables.
spanner-mycli now supports directed reads, a feature that allows you to read data from a specific replica of a Spanner database.
To use directed reads with spanner-mycli, you need to specify the --directed-read flag.
The --directed-read flag takes a single argument, which is the name of the replica that you want to read from.
The replica name can be specified in one of the following formats:
<replica_location><replica_location>:<replica_type>- DirectedReadOptions protobuf JSON (include or exclude replicas, multiple selections, and
autoFailoverDisabled)
The <replica_location> specifies the region where the replica is located such as us-central1, asia-northeast2.
The <replica_type> specifies the type of the replica either READ_WRITE or READ_ONLY.
The location[:type] shorthand always sets one include replica with autoFailoverDisabled true. Use protobuf JSON for exclude replicas, multiple selections, or autoFailoverDisabled false.
$ spanner-mycli -p myproject -i myinstance -d mydb --directed-read us-central1
$ spanner-mycli -p myproject -i myinstance -d mydb --directed-read us-central1:READ_ONLY
$ spanner-mycli -p myproject -i myinstance -d mydb --directed-read asia-northeast2:READ_WRITE
Directed reads apply to supported read-only queries, including autocommit SELECT, explicit read-only transactions, partition queries, DUMP data-plane catalog/column/row reads, and metadata completions. They do not apply to read-write SELECT or DML, heartbeat, or partitioned DML.
Runtime SET DIRECTED_READ accepts the same location or location:READ_ONLY|READ_WRITE shorthand as --directed-read, or DirectedReadOptions protobuf JSON. An empty value clears the option. SHOW prints shorthand when that form is lossless; otherwise it prints protobuf JSON. SET is rejected while a transaction is pending or active; SET LOCAL is not supported. Unknown or malformed JSON fields are rejected and leave the current value unchanged.
Note
If you specify an incorrect region or type for directed reads, directed reads will not be enabled and your requsts won't be routed as expected. For example, in a multi-region configuration nam3, if you mistype us-east1 as us-east-1, the connection will succeed, but directed reads will not be enabled.
To perform directed reads to asia-northeast2 in a multi-region configuration asia1, you need to specify asia-northeast2 or asia-northeast2:READ_WRITE.
Since the replicas placed in asia-northeast2 are READ_WRITE replicas, directed reads will not be enabled if you specify asia-northeast2:READ_ONLY.
Please refer to the Spanner documentation to verify the valid configurations.
spanner-mycli supports all Spanner GoogleSQL and Spanner Graph statements, as well as several client-side statements.
Note: If any valid Spanner statement can't be executed, it is a bug.
In the following syntax, we use <> for a placeholder, [] for an optional keyword,
and {A|B|...} for a mutually exclusive keyword.
- The syntax is case-insensitive.
| Usage | Syntax | Note |
|---|---|---|
| Switch database | USE <database> [ROLE <role>]; |
The role you set is used for accessing with fine-grained access control. |
| Detach from database | DETACH; |
Switch to detached mode, disconnecting from the current database. |
| Drop database | DROP DATABASE <database>; |
|
| List databases | SHOW DATABASES; |
|
| Show DDL of the schema object | SHOW CREATE <type> <fqn>; |
|
| List tables | SHOW TABLES [<schema>]; |
If schema is not provided, the default schema is used |
| List change streams | SHOW CHANGE STREAMS; |
Columns are Schema, Name, and All (TRUE if the stream tracks the entire database). Use SHOW CREATE CHANGE STREAM <name> for configuration. |
| Show columns | SHOW COLUMNS FROM <table_fqn>; |
|
| Show indexes | SHOW INDEX FROM <table_fqn>; |
|
| SHOW DDLs | SHOW DDLS; |
|
| Export database DDL and data as SQL statements | DUMP DATABASE; |
Exports DDL plus BASE TABLE data from the default schema and named schemas. Views and synonyms are omitted from data. Catalog, column, and row reads share one read-only transaction. Requires spanner.databases.getDdl; that admin RPC is a fresh GetDatabaseDdl call and is not timestamp-bound to the dump transaction. When the admin response includes proto descriptors, prepends SET PROTO_DESCRIPTORS before rewritten DDL. CLI_DUMP_CYCLIC_MODE defaults to REJECT for populated cyclic FK/interleave groups, including all-NULL or row-acyclic data. Opt-in MUTATE pre-encodes all cyclic groups before output, then emits one unsplit transaction per populated group. No service-quota prediction or globally atomic restore; earlier work may remain committed. |
| Export database DDL only as SQL statements | DUMP SCHEMA; |
Exports a fresh GetDatabaseDdl response as SQL. Requires spanner.databases.getDdl. When the admin response includes proto descriptors, prepends SET PROTO_DESCRIPTORS so CREATE PROTO BUNDLE replay is self-contained. |
| Export specific tables as SQL statements | DUMP TABLES <table1> [, <table2>, ...]; |
Table names are [<schema>.]<table>. Data only; no constraint changes or implicit inclusion of other tables. Invalid names are rejected before GetDatabaseDdl. Requires spanner.databases.getDdl only when a selected interleaved child has another selected BASE TABLE whose name matches the catalog parent basename. CLI_DUMP_CYCLIC_MODE defaults to REJECT; opt-in MUTATE pre-encodes selected cyclic groups and emits one unsplit transaction per populated group. Omitted parents and prerequisite target rows remain caller responsibilities. No service-quota prediction; earlier restore work may remain committed. |
| Export tables matching GoogleSQL LIKE/EXCEPT patterns | DUMP TABLES LIKE <string> [, <string> ...] [EXCEPT <string> [, <string> ...]]; |
STRING literals only (including raw and triple-quoted). SQL-literal escaping is applied first; then native case-sensitive LIKE treats % and _ as wildcards and \ as the escape. Patterns match IF(TABLE_SCHEMA='', TABLE_NAME, CONCAT(TABLE_SCHEMA, '.', TABLE_NAME)), not a reconstructed identifier. LIKE patterns are unioned; EXCEPT subtracts from that set or from all base tables. Empty '' is a real pattern. Zero matches is an error. Data only; omitted INTERLEAVE/FK parents are not added. Quoted identifiers named LIKE or EXCEPT stay exact-list names. |
| Export all base tables except those matching GoogleSQL LIKE patterns | DUMP TABLES EXCEPT <string> [, <string> ...]; |
Same matching and safety rules as DUMP TABLES LIKE. DUMP DATABASE remains the no-selector all-tables command. |
| Show schema update operations | SHOW SCHEMA UPDATE OPERATIONS; |
|
| Show specific operation (async) | SHOW OPERATION <operation-id-or-name> [ASYNC|SYNC]; |
Attach to and monitor a specific Long Running Operation by its operation ID or full operation name. ASYNC (default) returns current status, SYNC provides real-time monitoring (planned). |
| Add split points | ADD SPLIT POINTS [EXPIRED AT <timestamp>] {TABLE|INDEX} <fqn> (<key>, ...) [TableKey (<key>, ...)] ...; |
TableKey is valid only for INDEX entries. |
| Drop split points | DROP SPLIT POINTS {TABLE|INDEX} <fqn> (<key>, ...) [TableKey (<key>, ...)] ...; |
TableKey is valid only for INDEX entries. |
| Show split points | SHOW SPLIT POINTS; |
|
| Show local proto descriptors | SHOW LOCAL PROTO; |
|
| Show remote proto bundle | SHOW REMOTE PROTO; |
|
| Pull remote proto descriptors | PULL REMOTE PROTO {ALL|<type>|(<type> [, <type>]...)}; |
Loads selected remote PROTO/ENUM files and their import closure into the local descriptor store. Prefers remote files and reuses matching local imports. Nested names still load the whole containing file. Does not delete complete local types or submit remote DDL. Uses a fresh Admin GetDatabaseDdl read. Returns SHOW LOCAL PROTO rows for the selected roots. |
| Manipulate PROTO BUNDLE | SYNC PROTO BUNDLE [{[RECURSIVE] UPSERT|DELETE} (<type> ...)]...; |
RECURSIVE modifies only the following UPSERT and expands lexically nested local messages/enums. It is not native Spanner syntax and does not follow references or imports. |
| Truncate table | TRUNCATE TABLE <table_fqn>; |
Only rows are deleted. Note: Non-atomically because executed as a partitioned DML statement. |
| Show execution plan without execution | EXPLAIN [FORMAT=<format>] [WIDTH=<width>] [PRINT=<preset-or-sections>] <sql>; |
Options can be in any order. Spaces are not allowed before or after the =. |
| Execute query and show execution plan with profile | EXPLAIN ANALYZE [FORMAT=<format>] [WIDTH=<width>] [PRINT=<preset-or-sections>] <sql>; |
Options can be in any order. Spaces are not allowed before or after the =. |
| Show EXPLAIN [ANALYZE] of the last query without execution | EXPLAIN [ANALYZE] [FORMAT=<format>] [WIDTH=<width>] [PRINT=<preset-or-sections>] LAST QUERY; |
Options can be in any order. Spaces are not allowed before or after the =. |
| Show the specific raw plan node from the last cached query plan | SHOW PLAN NODE <node_id>; |
Requires a preceding query or EXPLAIN ANALYZE. |
| Export the last cached query plan as ProtoJSON or ProtoJSON-equivalent YAML | SHOW LAST QUERY PLAN [WITH STATS] [INTO <path>]; |
Writes YAML for .yaml/.yml paths and ProtoJSON otherwise. Quoted paths are supported. Requires a preceding query or EXPLAIN ANALYZE. Intended for external plan viewers; not a stable versioned contract. |
| Show result shape without execution | DESCRIBE <sql>; |
|
| Partitioned DML | PARTITIONED {UPDATE|DELETE} ...; |
|
| Show partition tokens of partition query | PARTITION <sql>; |
Experimental. Each token is a complete native envelope for RUN PARTITION (smycli-part/1/...), not a bare partition token. Client-side validity is one hour. |
| Run partitioned query | RUN PARTITIONED QUERY <sql>; |
|
| Run a specific partition | RUN PARTITION <token>; |
Experimental. The token is the complete native envelope exported by PARTITION. Requires an idle session (no live transaction or manual batch). Client-side validity is one hour. Not an authentication grant and not Java/JDBC compatible. |
| Test root-partitionable | TRY PARTITIONED QUERY <sql>; |
|
| Start R/W transaction | BEGIN RW [TRANSACTION] [ISOLATION LEVEL {SERIALIZABLE|REPEATABLE READ}] [PRIORITY {HIGH|MEDIUM|LOW}]; |
(spanner-cli style); See Request Priority for details on the priority. |
| Start R/O transaction | BEGIN RO [TRANSACTION] [{<seconds>|<rfc3339_timestamp>}] [PRIORITY {HIGH|MEDIUM|LOW}]; |
<seconds> and <rfc3339_timestamp> is used for stale read. <rfc3339_timestamp> must be quoted. See Request Priority for details on the priority. |
| Start transaction | BEGIN [TRANSACTION] [ISOLATION LEVEL {SERIALIZABLE|REPEATABLE READ}] [PRIORITY {HIGH|MEDIUM|LOW}]; |
(Spanner JDBC driver style); It respects READONLY system variable. See Request Priority for details on the priority. |
| Commit R/W transaction or end R/O Transaction | COMMIT [TRANSACTION]; |
|
| Create a savepoint in the current explicit transaction | SAVEPOINT <name>; |
Requires CLI_SAVEPOINT_SUPPORT=ENABLED. Client-emulated replay, not a native Spanner savepoint. Rejected while a manual batch is open. |
| Roll the current explicit transaction back to a savepoint | ROLLBACK [TRANSACTION] TO [SAVEPOINT] <name>; |
Keeps the named savepoint. Distinct from full ROLLBACK/CLOSE. |
| Release a savepoint without undoing later work | RELEASE [SAVEPOINT] <name>; |
|
| Rollback R/W transaction or end R/O transaction | ROLLBACK [TRANSACTION]; |
CLOSE can be used as a synonym of ROLLBACK. |
| Set transaction mode | SET TRANSACTION {READ ONLY|READ WRITE}; |
(Spanner JDBC driver style); Set transaction mode for the current transaction. |
| Show transaction isolation level | SHOW TRANSACTION ISOLATION LEVEL; |
Inspects the current logical transaction, including pending and SAVEPOINT recovery, without activating it. When idle, reports DEFAULT_ISOLATION_LEVEL. UNSPECIFIED means the database default; it is not a guessed server isolation. |
| Show transaction read-only flag | SHOW TRANSACTION READ ONLY; |
Inspects the current logical transaction, including pending and SAVEPOINT recovery, without activating it. When idle, reports READONLY. |
| Start DDL batching | START BATCH DDL; |
|
| Start DML batching | START BATCH DML; |
|
| Run active batch | RUN BATCH; |
|
| Abort active batch | ABORT BATCH [TRANSACTION]; |
|
| Reset all resettable session variables to their startup snapshots | RESET ALL; |
Restores values captured after defaults, config, flags, and --set, before --init-command / --init-command-add. File-backed descriptors/templates, opaque graphs, connection identity, stream handles, and unimplemented placeholders are excluded. Ordinary RESET is persistent like SET: after success it retires only the targeted LOCAL undo entries. |
| Reset a resettable session variable to its startup snapshot | RESET <name>; |
Restores the post-default/config/flag/--set startup value of one canonical name or alias. Same exclusions, guards, and LOCAL undo rules as RESET ALL, scoped to that variable. RESET LOCAL and SET x=DEFAULT are not supported. |
| Set variable for the current transaction | SET LOCAL <name> = <value>; |
|
| Set type query parameter | SET PARAM <name> <type>; |
Names are case-insensitive. Later SET PARAM of the same logical name keeps the first stored spelling. |
| Set variable | SET <name> = <value>; |
|
| Add value to variable | SET <name> += <value>; |
|
| Show variables | SHOW VARIABLES; |
|
| Show variable | SHOW VARIABLE <name>; |
|
| Set value query parameter | SET PARAM <name> = <value>; |
Names are case-insensitive. Later SET PARAM of the same logical name keeps the first stored spelling. Binding uses the first SQL occurrence's spelling without rewriting the statement. Direct parameter and STRUCT field references copy the current typed value. |
| Show query parameters | SHOW PARAMS; |
Lists names, kinds, and types without values. Use SHOW PARAM to inspect a value. |
| Show query parameter | SHOW PARAM <name>; |
Shows the type and abbreviated value of one parameter. Names are case-insensitive. |
| Unset query parameter | UNSET PARAM <name>; |
Names are case-insensitive; UNSET removes the logical parameter. |
| Perform write mutations | MUTATE <table_fqn> {INSERT|UPDATE|REPLACE|INSERT_OR_UPDATE} ...; |
|
| Perform delete mutations | MUTATE <table_fqn> DELETE ...; |
|
| Send a queue message | MUTATE <queue> SEND (key => <key>, payload => <literal> [, deliver_time => <timestamp_literal>]); |
One primary key in schema order; requires a queue-capable server. |
| Acknowledge a queue message | MUTATE <queue> ACK (key => <key> [, ignore_not_found => <bool>]); |
Missing messages fail by default; ignore_not_found defaults to FALSE. |
| Show sampled query plans | SHOW QUERY PROFILES; |
EARLY EXPERIMENTAL |
| Show the single sampled query plan | SHOW QUERY PROFILE <fingerprint>; |
EARLY EXPERIMENTAL |
| Show statement history | SHOW HISTORY; |
Lists recorded statements oldest first. Interactive sessions use the live readline history; noninteractive sessions read CLI_HISTORY_FILE. |
| Show recent statement history | SHOW HISTORY LIMIT <n>; |
LIMIT n returns the n most recent statements, oldest first within the window. |
| Show help | HELP; |
|
| Show help for variables | HELP VARIABLES; |
|
| Show help for a variable or task (OUTPUT or KEYS) | HELP <name>; |
|
| Exit CLI | EXIT; |
|
| Compose query using LLM | GEMINI "<prompt>"; |
|
| Execute CQL | CQL ...; |
EARLY EXPERIMENTAL |
| Execute BigQuery SQL | BIGQUERY <sql>; |
READONLY sessions allow BIGQUERY scripts only when every statement is a query (SELECT, WITH, or FROM-pipe / parenthesized query). Mixed or unrecognized payloads are rejected locally before a BigQuery job is created. |
Meta commands are special commands that start with a backslash (\) and are processed by the CLI itself rather than being sent to Spanner. They are terminated by a newline rather than a semicolon, following the official spanner-cli style.
Note: Meta commands are only supported in interactive mode. They cannot be used in batch mode (with --execute or --file flags).
| Command | Description | Example |
|---|---|---|
\? |
Show keyboard shortcuts and meta-command help | \? |
\! <shell_command> |
Execute a system shell command | \! ls -la |
\. <filename> |
Execute SQL statements from a file | \. script.sql |
\R <prompt_string> |
Change the prompt string | \R mycli> |
\u <database> |
Switch to a different database | \u mydb |
\T <filename> |
Start tee logging to file (both screen and file) | \T output.txt |
\t |
Stop tee logging | \t |
\o <filename> |
Redirect query/data output to file (overwrite existing file) | \o backup.sql |
\o |
Disable output redirect (return to screen) | \o |
\O |
Disable output redirect (alternative to \o) |
\O |
For detailed documentation on each meta command, see docs/meta_commands.md.
You can customize the prompt by --prompt option or CLI_PROMPT system variable.
There are some escape sequences for being used in prompt.
Escape sequences:
%p: GCP Project ID%i: Cloud Spanner Instance ID%d: Cloud Spanner Database ID (shows*detached*when in detached mode)%t: In transaction mode(ro txn)or(rw txn)%n: Newline%%: Character%%{VAR_NAME}: System variable expansion
Example:
$ spanner-mycli -p myproject -i myinstance -d mydb --prompt='[%p:%i:%d]%n%t%% '
Connected: project="myproject", instance="myinstance", database="mydb", role="(default)", endpoint="(client default)"
[myproject:myinstance:mydb]
%
[myproject:myinstance:mydb]
% SELECT * FROM users ORDER BY id ASC;
+----+------+--------+
| id | name | active |
+----+------+--------+
| 1 | foo | true |
| 2 | bar | false |
+----+------+--------+
2 rows in set (3.09 msecs)
[myproject:myinstance:mydb]
% begin;
Query OK, 0 rows affected (0.08 sec)
[myproject:myinstance:mydb]
(rw txn)% ...
The default prompt is spanner%t> : it stays compact and shows transaction
state without including the database name.
To include the current database (or *detached*), opt in with
--prompt='spanner:%d%t> ' or SET CLI_PROMPT = 'spanner:%d%t> ';.
Long database names can make this custom prompt wide.
Startup and successful interactive USE commands print the project, instance,
database, role, and configured endpoint regardless of the prompt setting.
(client default) means no endpoint was explicitly configured.
%Pis substituted with padding to align the primary prompt.%Ris substituted with the current waiting status.
| Prompt | Meaning |
|---|---|
- |
Waiting for terminating multi-line query by ; |
''' |
Waiting for terminating multi-line single-quoted string literal by ''' |
""" |
Waiting for terminating multi-line double-quoted string literal by """ |
/* |
Waiting for terminating multi-line comment by */ |
spanner> SELECT """
"""> test
"""> """ AS s,
-> '''
'''> test
'''> ''' AS s2,
-> /*
/*>
/*> */
-> ;
The default prompt2 is %P%R> .
Continuation prompts must be nonempty, whether supplied through --prompt2,
the prompt2 configuration key, or SET CLI_PROMPT2. An explicit empty startup
value is rejected before --set overrides are applied. The primary prompt may
still be empty.
If you set only %P to prompt2, continuation prompt is only indentations.
It is convenient because of copy-and-paste friendly.
spanner> SET CLI_PROMPT2 = "%P";
Empty set (0.00 sec)
spanner> SELECT """
test
""" AS s;
This tool supports a TOML configuration file called .spanner_mycli.toml.
The config files are loaded from ~/.spanner_mycli.toml and ./.spanner_mycli.toml.
In the config file, you can set default option values for command line options.
TOML keys normally follow hyphen-separated flag names, for example vertexai-project for --vertexai-project.
For convenience, matching snake_case aliases such as vertexai_project are accepted too.
Example:
project = "myproject"
instance = "myinstance"
prompt = "[%p:%i:%d]%t> "For dedicated flags, TOML keys, and environment aliases, later sources override
earlier ones: built-in defaults, home .spanner_mycli.toml, current-directory
.spanner_mycli.toml, environment variables, then command-line flags.
--set NAME=VALUE applies after those sources. Initialization SQL runs after the
RESET startup snapshot has been captured, so its assignments are resettable.
Not every system variable has a flag, TOML key, or environment alias. See configuration sources and precedence for the full rules and RESET snapshots.
You can set request priority for command level or transaction level.
By default MEDIUM priority is used for every request.
To set a priority for command line level, you can use --priority={HIGH|MEDIUM|LOW} command line option or RPC_PRIORITY system variable.
To set a priority for transaction level, you can use PRIORITY {HIGH|MEDIUM|LOW} keyword.
Here are some examples for transaction-level priority.
# Read-write transaction with low priority
BEGIN PRIORITY LOW;
# Read-only transaction with low priority
BEGIN RO PRIORITY LOW;
# Read-only transaction with 60s stale read and medium priority
BEGIN RO 60 PRIORITY MEDIUM;
# Read-only transaction with exact timestamp and medium priority
BEGIN RO "2021-04-01T23:47:44+00:00" PRIORITY MEDIUM;
Note that transaction-level priority takes precedence over command-level priority.
Related transaction settings are documented in the variable reference:
- COMMIT_PRIORITY overrides Commit RPC priority; default
UNSPECIFIEDinherits the transaction priority. - KEEP_TRANSACTION_ALIVE controls explicit read-write heartbeats; default
TRUEpreserves existing behavior. - TRANSACTION_TIMEOUT adds a logical-transaction deadline, disabled by default. Its remaining budget is preserved across SAVEPOINT reconstruction and opt-in ABORTED retries.
See SAVEPOINT and retry limits for recovery guarantees.
You can set transaction tag using SET TRANSACTION_TAG = "<tag>", and request tag using SET STATEMENT_TAG = "<tag>".
TRANSACTION_TAG is the tag for the next physical read-write transaction. After that owner starts, SHOW VARIABLE TRANSACTION_TAG reports the applied tag. When the owner ends, the consumed slot is empty unless a SET LOCAL baseline remains: SET TRANSACTION_TAG = 'A' then pending SET LOCAL TRANSACTION_TAG = 'B' consumes B and restores A after COMMIT, ROLLBACK, or Close. An ordinary SET after SET LOCAL supersedes that baseline, so A then LOCAL B then SET C consumes C and does not restore A or B. SET and SET LOCAL of TRANSACTION_TAG are rejected while a physical read-write transaction is active. A pending BEGIN does not consume the tag, so BEGIN; then SET TRANSACTION_TAG still applies. Read-only transactions do not consume it; ordinary SET during RO stages the next RW slot, and SET LOCAL during RO follows the usual undo restoration. Partitioned DML does not currently send a transaction tag. STATEMENT_TAG is effective only for the next statement.
+------------------------------+
| BEGIN; |
| SET TRANSACTION_TAG = "tx1"; |
| |
| SET STATEMENT_TAG = "req1"; |
| SELECT val |
| FROM tab1 +--------------transaction_tag = tx1, request_tag = req1
| WHERE id = 1; |
| |
| SELECT * |
| FROM tab1; +--------------transaction_tag = tx1
| |
| SET STATEMENT_TAG = "req2"; |
| |
| UPDATE tab1 |
| SET val = 10 +--------------transaction_tag = tx1, request_tag = req2
| WHERE id = 1; |
| |
| COMMIT; +--------------transaction_tag = tx1
| |
| BEGIN; |
| SELECT 1; |
| COMMIT; +--------------no transaction_tag
+------------------------------+
This tool supports the Cloud Spanner Emulator via the SPANNER_EMULATOR_HOST environment variable.
$ export SPANNER_EMULATOR_HOST=localhost:9010
# Or with gcloud env-init:
$ $(gcloud emulators spanner env-init)
$ spanner-mycli -p myproject -i myinstance -d mydb
# Or use --endpoint with --insecure
$ unset SPANNER_EMULATOR_HOST
$ spanner-mycli -p myproject -i myinstance -d mydb --endpoint=localhost:9010 --insecureWarning
The Cloud Spanner Emulator has several limitations that affect spanner-mycli functionality. See the official emulator limitations documentation for a complete list. Notable limitations include:
- Split points operations (
ADD SPLIT POINTS,SHOW SPLIT POINTS,DROP SPLIT POINTS) are not supported EXPLAINandEXPLAIN ANALYZEwill error (emulator doesn't return query plans)
spanner-mycli supports connecting to regional endpoints for improved performance and reliability. You can specify a regional endpoint using either the --endpoint flag or the --host flag:
# Using --endpoint (requires both host and port)
$ spanner-mycli -p myproject -i myinstance -d mydb --endpoint=spanner.us-central1.rep.googleapis.com:443
# Using --host (port 443 is used by default)
$ spanner-mycli -p myproject -i myinstance -d mydb --host=spanner.us-central1.rep.googleapis.com
# Verify the endpoint configuration
spanner> SHOW VARIABLE CLI_ENDPOINT;
+--------------------------------------------+
| CLI_ENDPOINT |
+--------------------------------------------+
| spanner.us-central1.rep.googleapis.com:443 |
+--------------------------------------------+The --host and --port flags provide more flexibility:
- Use
--portalone to connect to a local emulator:--port=9010(implieslocalhost:9010) - Use
--hostalone to connect to a regional endpoint:--host=spanner.us-central1.rep.googleapis.com(implies port 443) - Use both for custom configurations:
--host=custom-host --port=9999
Note: --endpoint and the combination of --host/--port are mutually exclusive.
spanner-mycli supports a detached mode (admin operation only mode) that allows you to connect to a Cloud Spanner instance without initially connecting to a specific database. This is useful for performing instance-level administrative operations like creating or dropping databases.
You can start spanner-mycli in detached mode using the --detached flag:
$ spanner-mycli -p myproject -i myinstance --detached
Connected: project="myproject", instance="myinstance", database="*detached*", role="(default)", endpoint="(client default)"
spanner> SHOW DATABASES;
+----------------+
| Database |
+----------------+
| db1 |
| db2 |
+----------------+
2 rows in set (18.66 msecs)
spanner> CREATE DATABASE mydb;
Query OK, 0 rows affected (45.20 sec)You can switch between databases and detached mode during an interactive session:
# Connect to a database from detached mode
spanner> USE mydb;
Database changed: project="myproject", instance="myinstance", database="mydb", role="(default)", endpoint="(client default)"
spanner>
# Detach from database and return to detached mode
spanner> DETACH;
Detached from database
spanner>The database connection follows this priority order:
--detachedflag (highest priority) - forces detached mode--databasecommand line optionSPANNER_DATABASE_IDenvironment variable- Default behavior (detached mode if no database specified)
When in detached mode, you can only execute:
- Instance-level operations (
SHOW DATABASES,CREATE DATABASE,DROP DATABASE) - Client-side statements that don't require database access
USEstatements to connect to a database
Database-specific operations will fail with an error message indicating that no database is connected.
This section describes some notable features of spanner-mycli, they are not appeared in original spanner-cli.
The generated Name / Operations / Description inventory is
docs/system_variables.md
(make docs-update / HELP VARIABLES). That table is the reference; this
README section is not a second inventory and the examples below are not stored
defaults.
Some names are inspired by
Spanner JDBC connection properties.
That is a convenience mapping, not a claim that every property matches JDBC or
go-sql-spanner. Tracked deltas:
docs/spanner-driver-compatibility.md.
Startup order, RESET snapshots, and stored versus effective values:
docs/system_variables.md.
SHOW VARIABLES;
SHOW VARIABLE CLI_FORMAT;
SET CLI_FORMAT = 'VERTICAL';
SET LOCAL CLI_FORMAT = 'TAB';
SET CLI_DATABASE_DIALECT = 'GOOGLE_STANDARD_SQL';
SET STATEMENT_TIMEOUT = '2m';
SET CLI_IDLE_TRANSACTION_TIMEOUT = '60s';
SET RPC_PRIORITY = 'HIGH';
RESET STATEMENT_TIMEOUT;
RESET ALL;
HELP VARIABLES;
HELP CLI_FORMAT;
HELP OUTPUT;
HELP KEYS;Detailed values, examples, and restrictions are maintained in the variable reference:
- Queue SQL execution notes (experimental; requires a queue-capable server)
- Output formats and table streaming
- Statement timeout and idle transaction timeout
- Client metrics (opt-in; disabled by default)
- Fuzzy finder options
- Type styling and string quoting (default
NONE; CSV/TSV/JSONL/SQL exports are unchanged)
After a query or EXPLAIN ANALYZE has cached a plan, type SHOW PLAN NODE
and press Ctrl+T to select a node by ID, kind, or operator name. Only its ID
is inserted. Scalar nodes are included. This reads the same cache as
SHOW PLAN NODE without a network request; when no plan is cached, the input
is unchanged. It does not change when plans are cached or cleared (plain
EXPLAIN does not populate this cache).
Type SHOW QUERY PROFILE and press Ctrl+T to select a sampled fingerprint
from the current database's SPANNER_SYS.QUERY_PROFILES_TOP_HOUR. Searching
uses the fingerprint plus a one-line query-text preview (whitespace-normalized,
capped at 120 runes). Selecting inserts only the signed decimal fingerprint.
Duplicates keep the newest interval's preview. This is a bounded network
completion and is not cached. SHOW QUERY PROFILES is unchanged. A missing
session, no rows, fetch failure, cancellation, or an active read-write
transaction leaves the input unchanged.
Table width calculation and wrapping account for 7-bit ANSI escape sequences already present in values or headers, independently of CLI-added type styling. SGR color/style sequences are preserved across wrapped lines. Disabling CLI styling does not sanitize escape sequences in the input; CSV, JSONL and SQL export behavior is unchanged.
You can issue a batch DDL statements.
START BATCH DDLstarts DDL batching. Consecutive DDL statements are batched.RUN BATCHruns the batch.- or
ABORT BATCHaborts the batch.
Note: SET CLI_ECHO_EXECUTED_DDL = TRUE enables echo back of actual executed DDLs. It is useful to observe multiple schema versions in batch DDL.
spanner> START BATCH DDL;
Empty set (0.00 sec)
spanner> DROP INDEX IF EXISTS BatchExampleByCol;
Query OK, 0 rows affected (0.00 sec) (1 DDL in batch)
spanner> DROP TABLE IF EXISTS BatchExample;
Query OK, 0 rows affected (0.00 sec) (2 DDLs in batch)
spanner> CREATE TABLE BatchExample (PK INT64 PRIMARY KEY, Col INT64);
Query OK, 0 rows affected (0.00 sec) (3 DDLs in batch)
spanner> CREATE INDEX ExistingTableByCol ON ExistingTable(Col);
Query OK, 0 rows affected (0.00 sec) (4 DDLs in batch)
spanner> CREATE INDEX BatchExampleByCol ON BatchExample(Col);
Query OK, 0 rows affected (0.00 sec) (5 DDLs in batch)
spanner> RUN BATCH;
+--------------------------------------------------------------+-----------------------------+
| Executed | Commit Timestamp |
+--------------------------------------------------------------+-----------------------------+
| DROP INDEX IF EXISTS BatchExampleByCol; | 2024-12-29T09:09:00.404097Z |
| DROP TABLE IF EXISTS BatchExample; | 2024-12-29T09:09:00.404097Z |
| CREATE TABLE BatchExample (PK INT64 PRIMARY KEY, Col INT64); | 2024-12-29T09:09:00.404097Z |
| CREATE INDEX ExistingTableByCol ON ExistingTable(Col); | 2024-12-29T09:09:18.278025Z |
| CREATE INDEX BatchExampleByCol ON BatchExample(Col); | 2024-12-29T09:09:41.061034Z |
+--------------------------------------------------------------+-----------------------------+
Query OK, 0 rows affected (51.00 sec)
timestamp: 2024-12-29T09:09:41.061034Z
# or abort using ABORT BATCH.
You can use batch DML.
START BATCH DMLstarts DML batching. Consecutive DML statements are batched.RUN BATCHruns the batch.- or
ABORT BATCHaborts the batch.
spanner> START BATCH DML;
Empty set (0.00 sec)
spanner> DELETE FROM BatchExample WHERE TRUE;
Query OK, 0 rows affected (0.00 sec) (1 DML in batch)
spanner> INSERT INTO BatchExample (PK, Col) VALUES(1, 2);
Query OK, 0 rows affected (0.00 sec) (2 DMLs in batch)
spanner> UPDATE BatchExample SET Col = Col + 1 WHERE TRUE;
Query OK, 0 rows affected (0.00 sec) (3 DMLs in batch)
spanner> RUN BATCH;
+--------------------------------------------------+------+
| DML | Rows |
+--------------------------------------------------+------+
| DELETE FROM BatchExample WHERE TRUE | 1 |
| INSERT INTO BatchExample (PK, Col) VALUES(1, 2) | 1 |
| UPDATE BatchExample SET Col = Col + 1 WHERE TRUE | 1 |
+--------------------------------------------------+------+
Query OK, at most 3 rows affected (0.59 sec)
timestamp: 2024-12-29T17:25:48.616395+09:00
mutation_count: 9
# or abort using ABORT BATCH.
spanner-mycli can launch Cloud Spanner Emulator with empty database, powered by testcontainers.
$ spanner-mycli --embedded-emulator [--emulator-image=gcr.io/cloud-spanner-emulator/emulator:${VERSION}] [--emulator-platform=linux/amd64]
> SET CLI_PROMPT="%p:%i:%d%n> ";
Empty set (0.00 sec)
emulator-project:emulator-instance:emulator-database
> SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN ("INFORMATION_SCHEMA", "SPANNER_SYS");
+---------------+--------------+------------+-------------------+------------------+------------+---------------+-----------------+--------------------------------+
| TABLE_CATALOG | TABLE_SCHEMA | TABLE_NAME | PARENT_TABLE_NAME | ON_DELETE_ACTION | TABLE_TYPE | SPANNER_STATE | INTERLEAVE_TYPE | ROW_DELETION_POLICY_EXPRESSION |
| STRING | STRING | STRING | STRING | STRING | STRING | STRING | STRING | STRING |
+---------------+--------------+------------+-------------------+------------------+------------+---------------+-----------------+--------------------------------+
+---------------+--------------+------------+-------------------+------------------+------------+---------------+-----------------+--------------------------------+
Empty set (8.763167ms)
You can initialize the embedded runtime with Google's official sample databases using the --sample-database flag:
# List available sample databases
$ spanner-mycli --list-samples
Available sample databases:
banking GoogleSQL Banking application with accounts and transactions
finance GoogleSQL Finance application schema (GoogleSQL)
finance-graph GoogleSQL Finance application with graph features
finance-pg PostgreSQL Finance application (PostgreSQL dialect)
fingraph GoogleSQL Financial graph example demonstrating Spanner Graph features
gaming GoogleSQL Gaming application with players and scores
singers GoogleSQL Music database used throughout Spanner documentation
Usage: spanner-mycli --embedded-emulator --sample-database=<name>
spanner-mycli --embedded-omni --sample-database=<name>
spanner-mycli --embedded-emulator --sample-database=/path/to/metadata.yaml
spanner-mycli --embedded-omni --sample-database=/path/to/metadata.yaml
Sample metadata files may use .json, .yaml, or .yml.
# Start with the banking sample database
$ spanner-mycli --embedded-emulator --sample-database=banking
emulator-project:emulator-instance:emulator-database
> SELECT COUNT(*) AS count FROM Accounts;
+-------+
| count |
| INT64 |
+-------+
| 10 |
+-------+
1 rows in set (2.76 ms)
# Use embedded fingraph sample
$ spanner-mycli --embedded-emulator --sample-database=fingraph
# Load the same sample on embedded Omni
$ spanner-mycli --embedded-omni --sample-database=fingraph
# Use custom sample with metadata file
$ spanner-mycli --embedded-emulator --sample-database=/path/to/mysample.yaml
$ spanner-mycli --embedded-omni --sample-database=/path/to/mysample.yamlThe sample databases include both embedded samples (fingraph, singers) and samples downloaded from Google Cloud Storage. You can also create custom samples using metadata files in JSON or YAML format.
Note
The embedded emulator has the same limitations as the standalone emulator. See the warning in the Using with the Cloud Spanner Emulator section above for details.
spanner-mycli can also launch experimental Spanner Omni via spanemuboost.
$ spanner-mycli --embedded-omni
default:default:emulator-database
> SELECT 1--embedded-omni automatically configures the fixed single-server Omni project and instance defaults, reuses the backend-provided client options needed for the experimental host, and can load the same sample databases as the embedded emulator path. In Spanner Omni terminology, this embedded single-server setup corresponds to a deployment, which is the Omni equivalent of a Google Cloud Spanner instance; see Spanner Omni key terms. Before relying on embedded Omni locally, also check the official Spanner Omni system requirements.
Remote Omni endpoints that use a private CA or require a client certificate are configured at startup. An explicit --endpoint or --host is required; port-only and default Cloud routing are not enough. Custom TLS does not change Google authentication unless you also pass --without-authentication.
# Verified TLS to a named Omni endpoint. Google ADC/--credential stay in use
# unless --without-authentication is set.
spanner-mycli -p PROJECT -i INSTANCE -d DATABASE \
--endpoint=omni.example:443 \
--ca-cert-file=/path/to/ca.pem
# mTLS without Google bearer credentials for this Spanner endpoint.
# --without-authentication is not plaintext and does not skip certificate
# verification. It does not apply to BigQuery or Gemini clients.
spanner-mycli -p PROJECT -i INSTANCE -d DATABASE \
--endpoint=omni.example:443 \
--ca-cert-file=/path/to/ca.pem \
--client-cert-file=/path/to/client.pem \
--client-cert-key=/path/to/client.key \
--without-authentication--insecure remains plaintext gRPC and cannot be combined with these certificate files. SHOW VARIABLES reports the certificate paths only; private-key bytes are never displayed.
You can use --proto-descriptor-file option to specify proto descriptor file.
$ spanner-mycli -p myproject -i myinstance -d mydb --proto-descriptor-file=testdata/protos/order_descriptors.pb
Connected: project="myproject", instance="myinstance", database="mydb", role="(default)", endpoint="(client default)"
spanner> SHOW LOCAL PROTO;
+---------------------------------+-------+-------------------+--------------------+
| full_name | kind | package | file |
+---------------------------------+-------+-------------------+--------------------+
| examples.shipping.Order | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.Order.Address | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.Order.Item | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.OrderHistory | PROTO | examples.shipping | order_protos.proto |
+---------------------------------+-------+-------------------+--------------------+
4 rows in set (0.00 sec)
spanner> SET PROTO_DESCRIPTORS_FILE_PATH += "testdata/protos/query_plan_descriptors.pb";
Empty set (0.00 sec)
spanner> SHOW LOCAL PROTO;
+----------------------------------------------------------------+-------+-------------------+-----------------------------------------------+
| full_name | kind | package | file |
+----------------------------------------------------------------+-------+-------------------+-----------------------------------------------+
| examples.shipping.Order | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.Order.Address | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.Order.Item | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.OrderHistory | PROTO | examples.shipping | order_protos.proto |
| google.protobuf.Struct | PROTO | google.protobuf | google/protobuf/struct.proto |
| google.protobuf.Struct.FieldsEntry | PROTO | google.protobuf | google/protobuf/struct.proto |
| google.protobuf.Value | PROTO | google.protobuf | google/protobuf/struct.proto |
| google.protobuf.ListValue | PROTO | google.protobuf | google/protobuf/struct.proto |
| google.protobuf.NullValue | ENUM | google.protobuf | google/protobuf/struct.proto |
| google.spanner.v1.PlanNode | PROTO | google.spanner.v1 | googleapis/google/spanner/v1/query_plan.proto |
| google.spanner.v1.PlanNode.ChildLink | PROTO | google.spanner.v1 | googleapis/google/spanner/v1/query_plan.proto |
| google.spanner.v1.PlanNode.ShortRepresentation | PROTO | google.spanner.v1 | googleapis/google/spanner/v1/query_plan.proto |
| google.spanner.v1.PlanNode.ShortRepresentation.SubqueriesEntry | PROTO | google.spanner.v1 | googleapis/google/spanner/v1/query_plan.proto |
| google.spanner.v1.PlanNode.Kind | ENUM | google.spanner.v1 | googleapis/google/spanner/v1/query_plan.proto |
| google.spanner.v1.QueryPlan | PROTO | google.spanner.v1 | googleapis/google/spanner/v1/query_plan.proto |
+----------------------------------------------------------------+-------+-------------------+-----------------------------------------------+
15 rows in set (0.00 sec)
spanner> CREATE PROTO BUNDLE (`examples.shipping.Order`);
Query OK, 0 rows affected (6.34 sec)
spanner> SHOW REMOTE PROTO;
+-------------------------+-------+-------------------+
| full_name | kind | package |
+-------------------------+-------+-------------------+
| examples.shipping.Order | PROTO | examples.shipping |
+-------------------------+-------+-------------------+
1 rows in set (0.94 sec)
spanner> ALTER PROTO BUNDLE INSERT (`examples.shipping.Order.Item`);
Query OK, 0 rows affected (9.25 sec)
spanner> SHOW REMOTE PROTO;
+------------------------------+-------+-------------------+
| full_name | kind | package |
+------------------------------+-------+-------------------+
| examples.shipping.Order | PROTO | examples.shipping |
| examples.shipping.Order.Item | PROTO | examples.shipping |
+------------------------------+-------+-------------------+
2 rows in set (0.82 sec)
spanner> ALTER PROTO BUNDLE UPDATE (`examples.shipping.Order`);
Query OK, 0 rows affected (8.68 sec)
spanner> ALTER PROTO BUNDLE DELETE (`examples.shipping.Order.Item`);
Query OK, 0 rows affected (9.55 sec)
spanner> SHOW REMOTE PROTO;
+-------------------------+-------+-------------------+
| full_name | kind | package |
+-------------------------+-------+-------------------+
| examples.shipping.Order | PROTO | examples.shipping |
+-------------------------+-------+-------------------+
1 rows in set (0.79 sec)
PULL REMOTE PROTO copies available remote PROTO/ENUM descriptors into the local session store. It performs an Admin GetDatabaseDdl read and a local graph update. It never submits remote DDL.
PULL REMOTE PROTO examples.shipping.Order;
PULL REMOTE PROTO (examples.shipping.Order, examples.shipping.Customer);
PULL REMOTE PROTO ALL;
Names use GoogleSQL identifiers. Duplicate listed names keep the first occurrence. ALL selects complete remote PROTO/ENUM names and skips placeholder messages and synthetic map entries. An empty remote ALL is a no-op. A listed missing, placeholder, or map-entry name is an error and leaves local state unchanged.
Descriptors are installed at protobuf file granularity: selecting a name also loads the other definitions in that file plus the file's import closure. Nested names such as examples.shipping.Order.Item still load order_protos.proto as a whole. Remote files are preferred; if an import is absent remotely, a matching local file is reused. PULL does not invent missing fields or fetch extra network/filesystem dependencies. Unrelated remote files with missing imports do not block a complete selected component.
The same full type name in two different file names is a conflict. A complete remote file may replace its same-named local file when the merged candidate still validates. PULL does not delete complete local types or downgrade them to placeholders; use an explicit SET PROTO_DESCRIPTORS when removal is intended. The statement uses a fresh Admin read, not the 30-second SHOW/DDL cache. Successful graph changes clear PROTO_DESCRIPTORS_FILE_PATH; an identical pull leaves those paths unchanged.
The result uses the SHOW LOCAL PROTO row shape (full_name, kind, package, file) for the selected roots only. Dependencies may be loaded without appearing as output rows.
You can also use PROTO_DESCRIPTORS_FILE_PATH system variable to update or read the current proto descriptor file setting.
This replaces CLI_PROTO_DESCRIPTOR_FILE; the old variable name is no longer
accepted. Update SQL scripts and --set entries to the new name. The
--proto-descriptor-file flag and its configuration-file setting are unchanged.
ADD, .proto compilation, and HTTP(S), file://, and gs:// loading remain
supported. SET LOCAL is not supported because restoring the value would
reload external files.
spanner> SET PROTO_DESCRIPTORS_FILE_PATH = "./other_descriptors.pb";
Empty set (0.00 sec)
spanner> SHOW VARIABLE PROTO_DESCRIPTORS_FILE_PATH;
+-----------------------------+
| PROTO_DESCRIPTORS_FILE_PATH |
+-----------------------------+
| ./other_descriptors.pb |
+-----------------------------+
Empty set (0.00 sec)
PROTO_DESCRIPTORS holds the same effective graph as base64. SET PROTO_DESCRIPTORS replaces the graph and clears file provenance. DUMP SCHEMA and DUMP DATABASE emit that SET before DDL when the admin response includes descriptors, so a single SQL script can replay CREATE PROTO BUNDLE without a sidecar file. SET LOCAL is not supported, and SET is rejected while a manual batch is active.
(EXPERIMENTAL) It also supports non-compiled .proto files, including their transitive imports and standard protobuf imports.
Comma-separated inputs are merged into one descriptor graph before validation,
both for --proto-descriptor-file and SET PROTO_DESCRIPTORS_FILE_PATH. Later
inputs replace earlier descriptors with the same protobuf file name; file names
must therefore identify the intended version, even if its package changes.
The resulting graph must resolve all imports and symbols. Failed SET or ADD
leaves both the previous descriptor graph and file list unchanged; split binary
descriptor fragments must be supplied together in one SET, not as incomplete
intermediate ADD operations.
spanner> SET PROTO_DESCRIPTORS_FILE_PATH = "testdata/protos/order_protos.proto";
Empty set (0.00 sec)
spanner> SHOW LOCAL PROTO;
+---------------------------------+-------+-------------------+------------------------------------+
| full_name | kind | package | file |
+---------------------------------+-------+-------------------+------------------------------------+
| examples.shipping.Order | PROTO | examples.shipping | testdata/protos/order_protos.proto |
| examples.shipping.Order.Address | PROTO | examples.shipping | testdata/protos/order_protos.proto |
| examples.shipping.Order.Item | PROTO | examples.shipping | testdata/protos/order_protos.proto |
| examples.shipping.OrderHistory | PROTO | examples.shipping | testdata/protos/order_protos.proto |
+---------------------------------+-------+-------------------+------------------------------------+
4 rows in set (0.00 sec)
This feature is powered by bufbuild/protocompile.
(EXPERIMENTAL) .pb and .proto files can be loaded from HTTP(S), file://,
and gs:// URIs. Source vs binary is classified from the URI path; a query or
fragment cannot change that. HTTP(S) and gs:// keep the original URI as
identity and request target, including query encoding. file:// is a local-path
alias: the decoded path is compiled or unmarshaled so it shares descriptor
identity with the same bare path. Local non-URI names keep ordinary extension
semantics and are not parsed as URLs. Relative import paths are not rewritten
onto the remote host or bucket; only explicit http(s):// or gs:// import
strings are fetched. Remote loads use a 100 MiB cap. GCS uses configured
application default credentials.
spanner> SET PROTO_DESCRIPTORS_FILE_PATH = "https://github.com/apstndb/spanner-mycli/raw/refs/heads/main/testdata/protos/order_descriptors.pb";
Empty set (0.68 sec)
[gcpug-public-spanner:merpay-sponsored-instance:apstndb-sampledb3]
spanner> SHOW LOCAL PROTO;
+---------------------------------+-------+-------------------+--------------------+
| full_name | kind | package | file |
+---------------------------------+-------+-------------------+--------------------+
| examples.shipping.Order | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.Order.Address | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.Order.Item | PROTO | examples.shipping | order_protos.proto |
| examples.shipping.OrderHistory | PROTO | examples.shipping | order_protos.proto |
+---------------------------------+-------+-------------------+--------------------+
4 rows in set (0.00 sec)
Note: SET CLI_ECHO_EXECUTED_DDL = TRUE enables echo back of actual executed DDLs.
spanner> SET CLI_ECHO_EXECUTED_DDL = TRUE;
Empty set (0.00 sec)
SYNC PROTO BUNDLE UPSERT (FullName, ...) simplifies use of CREATE PROTO BUNDLE and ALTER PROTO BUNDLE {INSERT|UPDATE}.
SYNC PROTO BUNDLE executes ALTER PROTO BUNDLE INSERT or ALTER PROTO BUNDLE UPDATE in UPSERT semantics.
spanner> SHOW REMOTE PROTO;
+-------------------------+-------+-------------------+
| full_name | kind | package |
+-------------------------+-------+-------------------+
| examples.shipping.Order | PROTO | examples.shipping |
+-------------------------+-------+-------------------+
1 rows in set (0.87 sec)
spanner> SYNC PROTO BUNDLE UPSERT (`examples.shipping.Order`, examples.shipping.OrderHistory);
+------------------------------------------------------------------------------------------------+
| executed |
+------------------------------------------------------------------------------------------------+
| ALTER PROTO BUNDLE INSERT (examples.shipping.OrderHistory) UPDATE (examples.shipping.`Order`); |
+------------------------------------------------------------------------------------------------+
Query OK, 0 rows affected (8.57 sec)
If SYNC PROTO BUNDLE UPSERT is executed on empty proto bundle,
CREATE PROTO BUNDLE will be executed instead of ALTER PROTO BUNDLE.
spanner> SHOW REMOTE PROTO;
Empty set (0.89 sec)
spanner> SYNC PROTO BUNDLE UPSERT (`examples.shipping.Order`);
+--------------------------------------------------+
| executed |
+--------------------------------------------------+
| CREATE PROTO BUNDLE (examples.shipping.`Order`); |
+--------------------------------------------------+
Query OK, 0 rows affected (8.27 sec)
RECURSIVE modifies only the next UPSERT clause. It expands a local root plus message and enum types whose full names are lexically nested under root. (Order.Address, Order.Item). It does not follow field references, imports, or similarly prefixed siblings such as OrderHistory. This is CLI-generated ordinary CREATE/ALTER/DROP PROTO BUNDLE SQL, not native Spanner recursive syntax.
Placeholder and synthetic map-entry messages are omitted from automatic expansion. A selected enum contributes itself only. Missing, placeholder, and map-entry roots fail against the current local FileDescriptorSet before Admin GetDatabaseSchema or DDL commit.
SYNC PROTO BUNDLE RECURSIVE UPSERT (examples.shipping.Order);
-- expands Order, Order.Address, Order.Item when those exist locally
SYNC PROTO BUNDLE RECURSIVE UPSERT (examples.shipping.Order) UPSERT (examples.shipping.OrderHistory);
-- first clause is recursive; the plain UPSERT stays non-recursive
SYNC PROTO BUNDLE UPSERT (examples.shipping.Order.Item) RECURSIVE UPSERT (examples.shipping.Order);
-- Item is kept at its earlier position; later recursive expansion appends not-yet-selected descendants
A mixed DELETE after expansion is rejected when the same full name appears in both operations (for example RECURSIVE UPSERT (Order) plus DELETE (Order.Item)). Names keep first-occurrence order within UPSERT and within DELETE.
SYNC PROTO BUNDLE DELETE (<full_name>, ...) executes PROTO BUNDLE DELETE only existing types(DELETE IF EXISTS semantics).
spanner> SHOW REMOTE PROTO;
+--------------------------------+-------+-------------------+
| full_name | kind | package |
+--------------------------------+-------+-------------------+
| examples.shipping.Order | PROTO | examples.shipping |
| examples.shipping.OrderHistory | PROTO | examples.shipping |
+--------------------------------+-------+-------------------+
2 rows in set (0.87 sec)
spanner> SYNC PROTO BUNDLE DELETE (`examples.shipping.Order`, examples.UnknownType);
+--------------------------------------------------------+
| executed |
+--------------------------------------------------------+
| ALTER PROTO BUNDLE DELETE (examples.shipping.`Order`); |
+--------------------------------------------------------+
Query OK, 0 rows affected (8.86 sec)
spanner> SYNC PROTO BUNDLE DELETE (examples.UnknownType);
Query OK, 0 rows affected (0.79 sec)
If SYNC PROTO BUNDLE DELETE will delete the last Protocol Buffers type in the proto bundle,
DROP PROTO BUNDLE will be executed instead of ALTER PROTO BUNDLE.
spanner> SHOW REMOTE PROTO;
+-------------------------+-------+-------------------+
| full_name | kind | package |
+-------------------------+-------+-------------------+
| examples.shipping.Order | PROTO | examples.shipping |
+-------------------------+-------+-------------------+
1 rows in set (0.87 sec)
spanner> SYNC PROTO BUNDLE DELETE (`examples.shipping.OrderHistory`);
+--------------------+
| executed |
+--------------------+
| DROP PROTO BUNDLE; |
+--------------------+
Query OK, 0 rows affected (8.24 sec)
SYNC PROTO BUNDLE accepts any number of UPSERT and DELETE clauses in either order as one statement. The client reads the current bundle once and composes a single CREATE PROTO BUNDLE, ALTER PROTO BUNDLE, DROP PROTO BUNDLE, or no-op. It does not run the clauses as two separately executed operations.
Illustrative input and composed SQL (not a live session transcript):
SYNC PROTO BUNDLE UPSERT (examples.NewType) DELETE (examples.OldType);
When examples.NewType is absent from the current bundle and examples.OldType is present, that input composes:
ALTER PROTO BUNDLE INSERT (examples.NewType) DELETE (examples.OldType);
Names are set-wise per operation: repeated names in UPSERT or in DELETE keep the first occurrence. The same full name in both UPSERT and DELETE is rejected after RECURSIVE expansion and before Admin schema reads. Official GoogleSQL documents INSERT, UPDATE, and DELETE on one ALTER PROTO BUNDLE as one atomic type-information change; that does not make the preceding schema read or a START BATCH DDL payload atomic.
Loaded proto descriptors are used for formatting PROTO and ENUM column values.
spanner> SELECT p, p.*
FROM (
SELECT AS VALUE NEW examples.spanner.music.SingerInfo{singer_id: 1, birth_date: "1970-01-01", nationality: "Japan", genre: "POP"}
) AS p;
+----------------------------------+-----------+------------+-------------+-------------+
| p | singer_id | birth_date | nationality | genre |
| PROTO<SingerInfo> | INT64 | STRING | STRING | ENUM<Genre> |
+----------------------------------+-----------+------------+-------------+-------------+
| CAESCjE5NzAtMDEtMDEaBUphcGFuIAA= | 1 | 1970-01-01 | Japan | 0 |
+----------------------------------+-----------+------------+-------------+-------------+
1 rows in set (0.33 msecs)
spanner> SET PROTO_DESCRIPTORS_FILE_PATH += "testdata/protos/singer.proto";
Empty set (0.00 sec)
spanner> SHOW LOCAL PROTO;
+-----------------------------------------+-------+------------------------+------------------------------+
| full_name | kind | package | file |
+-----------------------------------------+-------+------------------------+------------------------------+
| examples.spanner.music.SingerInfo | PROTO | examples.spanner.music | testdata/protos/singer.proto |
| examples.spanner.music.CustomSingerInfo | PROTO | examples.spanner.music | testdata/protos/singer.proto |
| examples.spanner.music.Genre | ENUM | examples.spanner.music | testdata/protos/singer.proto |
| examples.spanner.music.CustomGenre | ENUM | examples.spanner.music | testdata/protos/singer.proto |
+-----------------------------------------+-------+------------------------+------------------------------+
4 rows in set (0.00 sec)
spanner> SELECT p, p.*
FROM (
SELECT AS VALUE NEW examples.spanner.music.SingerInfo{singer_id: 1, birth_date: "1970-01-01", nationality: "Japan", genre: "POP"}
) AS p;
+-------------------------------------------------------------------+-----------+------------+-------------+-------------+
| p | singer_id | birth_date | nationality | genre |
| PROTO<SingerInfo> | INT64 | STRING | STRING | ENUM<Genre> |
+-------------------------------------------------------------------+-----------+------------+-------------+-------------+
| singer_id:1 birth_date:"1970-01-01" nationality:"Japan" genre:POP | 1 | 1970-01-01 | Japan | POP |
+-------------------------------------------------------------------+-----------+------------+-------------+-------------+
1 rows in set (0.87 msecs)
You can enable multiline format.
spanner> SET CLI_PROTOTEXT_MULTILINE=TRUE;
Empty set (0.00 sec)
spanner> SELECT p, p.*
FROM (
SELECT AS VALUE NEW examples.spanner.music.SingerInfo{singer_id: 1, birth_date: "1970-01-01", nationality: "Japan", genre: "POP"}
) AS p;
+--------------------------+-----------+------------+-------------+-------------+
| p | singer_id | birth_date | nationality | genre |
| PROTO<SingerInfo> | INT64 | STRING | STRING | ENUM<Genre> |
+--------------------------+-----------+------------+-------------+-------------+
| singer_id: 1 | 1 | 1970-01-01 | Japan | POP |
| birth_date: "1970-01-01" | | | | |
| nationality: "Japan" | | | | |
| genre: POP | | | | |
+--------------------------+-----------+------------+-------------+-------------+
1 rows in set (3.37 msecs)
You can list schema update operations and their progress.
Note: PROGESS column is not populated for done operations due to avoid N+1 API calls.
spanner> SHOW SCHEMA UPDATE OPERATIONS;
+---------------------------+---------------------------------------------------+-------+----------+-----------------------------+-------+
| OPERATION_ID | STATEMENTS | DONE | PROGRESS | COMMIT_TIMESTAMP | ERROR |
+---------------------------+---------------------------------------------------+-------+----------+-----------------------------+-------+
| _auto_op_0175680d57629380 | CREATE INDEX SongsBySongName3 ON Songs(SongName); | false | 100 | 2025-05-02T14:43:14.658597Z | |
| | CREATE INDEX SongsBySongName4 ON Songs(SongName); | | 17 | | |
+---------------------------+---------------------------------------------------+-------+----------+-----------------------------+-------+
1 rows in set (1.88 sec)
Warning
Split points operations (ADD SPLIT POINTS, SHOW SPLIT POINTS, DROP SPLIT POINTS) are not supported by the Cloud Spanner Emulator and will result in an error. See emulator limitations for details.
spanner-mycli can manage split points for pre-splitting.
You can add split points using ADD SPLIT POINTS statement.
- You can add multiple split points in a statement.
- Table and index split points can use composite keys.
- Index split points can include the complete table primary key with
TableKey (...). - You can specify the expiration of split points.
-- split point for table key with default expiration
spanner> ADD SPLIT POINTS
TABLE Singers (42);
-- split point for table key in named schema
spanner> ADD SPLIT POINTS EXPIRED AT "2025-05-05T00:00:00"
TABLE sch1.Singers (21);
spanner> ADD SPLIT POINTS EXPIRED AT "2025-05-05T00:00:00Z"
-- split point for index key
INDEX SingersByFirstLastName ("John", "Doe")
-- split point for index key with table primary key
INDEX SingersByFirstLastName ("Mary", "Sue") TableKey (12);
The entry structure follows gcloud spanner databases splits add --splits-file, including composite keys and the optional index TableKey (...) suffix. Literal syntax differs as follows:
- It is implemented using memefish so it follows GoogleSQL lexical structure.
INT64andNUMERICSpanner data types can be integer literal. For example,123or99.99- Floating number values need to be written as float literal. For example,
1.0or1.287 - Even if the split value needs to have a comma, you must not escape the comma.
You can show defined split points using SHOW SPLIT POINTS statement.
spanner> SHOW SPLIT POINTS;
+--------------+------------------------+---------------------+-----------------------------------------------------------------------------------------------+-----------------------------+
| TABLE_NAME | INDEX_NAME | INITIATOR | SPLIT_KEY | EXPIRE_TIME |
| STRING | STRING | STRING | STRING | TIMESTAMP |
+--------------+------------------------+---------------------+-----------------------------------------------------------------------------------------------+-----------------------------+
| Singers | | cloud_spanner_mycli | Singers(42) | 2025-05-09T11:29:43.928097Z |
| Singers | SingersByFirstLastName | cloud_spanner_mycli | Index: SingersByFirstLastName on Singers, Index Key: (John,Doe), Primary Table Key: (<begin>) | 2025-05-05T00:00:00Z |
| Singers | SingersByFirstLastName | cloud_spanner_mycli | Index: SingersByFirstLastName on Singers, Index Key: (Mary,Sue), Primary Table Key: (12) | 2025-05-05T00:00:00Z |
| sch1.Singers | | cloud_spanner_mycli | sch1.Singers(21) | 2025-05-05T00:00:00Z |
+--------------+------------------------+---------------------+-----------------------------------------------------------------------------------------------+-----------------------------+
4 rows in set (0.26 sec)
You can drop split points using DROP SPLIT POINTS statement.
spanner> DROP SPLIT POINTS
INDEX SingersByFirstLastName ("Mary", "Sue") TableKey (12);
-
Query OK, 0 rows affected (0.26 sec)
spanner> SHOW SPLIT POINTS;
+--------------+------------------------+---------------------+-----------------------------------------------------------------------------------------------+-----------------------------+
| TABLE_NAME | INDEX_NAME | INITIATOR | SPLIT_KEY | EXPIRE_TIME |
| STRING | STRING | STRING | STRING | TIMESTAMP |
+--------------+------------------------+---------------------+-----------------------------------------------------------------------------------------------+-----------------------------+
| Singers | | cloud_spanner_mycli | Singers(42) | 2025-05-09T11:29:43.928097Z |
| Singers | SingersByFirstLastName | cloud_spanner_mycli | Index: SingersByFirstLastName on Singers, Index Key: (John,Doe), Primary Table Key: (<begin>) | 2025-05-05T00:00:00Z |
| sch1.Singers | | cloud_spanner_mycli | sch1.Singers(21) | 2025-05-05T00:00:00Z |
+--------------+------------------------+---------------------+-----------------------------------------------------------------------------------------------+-----------------------------+
3 rows in set (0.02 sec)
Customize result detail lines with a Go text/template passed to --output-template.
The bundled output_full.tmpl is the complete
example for OutputContext and spanstats.QueryStats; copy it to
output_timestamp.tmpl and edit it to make a custom template.
Prefer ReadTimestamp and CommitTimestamp; Timestamp is a compatibility
alias for whichever is present.
spanner-mycli -t -v --output-template output_timestamp.tmpl -e 'SELECT 1'spanner-mycli utilizes memefish as:
- statement type detector
- comment stripper
- statement separator
Statement type detector behavior can be controlled by the CLI_PARSE_MODE system variable.
| CLI_PARSE_MODE | Description |
|---|---|
| NO_MEMEFISH | Detect statement kind lexically (default) |
| FALLBACK | Use memefish, then lexical detection on failure |
| MEMEFISH_ONLY | Use memefish without fallback |
NO_MEMEFISH still uses memefish for input splitting and comment handling; it only skips memefish parsing for statement kind detection. UNSPECIFIED acts like the default. Parser fallback details are logged only at DEBUG level.
spanner> SET CLI_PARSE_MODE = "MEMEFISH_ONLY";
Empty set (0.00 sec)
spanner> SELECT * FRM 1;
ERROR: invalid statement: syntax error: :1:10: expected token: <eof>, but: <ident>
1: SELECT * FRM 1
^~~
spanner> SET CLI_PARSE_MODE = "FALLBACK";
Empty set (0.00 sec)
spanner> SELECT * FRM 1;
ERROR: spanner: code = "InvalidArgument", desc = "Syntax error: Expected end of input but got identifier \\\"FRM\\\" [at 1:10]\\nSELECT * FRM 1\\n ^"
spanner> SET CLI_PARSE_MODE = "NO_MEMEFISH";
Empty set (0.00 sec)
spanner> SELECT * FRM 1;
ERROR: spanner: code = "InvalidArgument", desc = "Syntax error: Expected end of input but got identifier \\\"FRM\\\" [at 1:10]\\nSELECT * FRM 1\\n ^"
spanner-mycli supports mutations.
Mutations are buffered in read-write transaction, or immediately commit outside explicit transaction.
MUTATE <table_fqn> {INSERT|UPDATE|REPLACE|INSERT_OR_UPDATE} {<struct_literal> | <array_of_struct_literal>};
MUTATE <table_fqn> DELETE ALL;
MUTATE <table_fqn> DELETE {<tuple_struct_literal> | <array_of_tuple_struct_literal>};
MUTATE <table_fqn> DELETE KEY_RANGE({start_closed | start_open} => <tuple_struct_literal>,
{end_closed | end_open} => <tuple_struct_literal>);
Note: In this context, parenthesized expression and some simple literals are treated as a single field struct literal.
Delete keys also accept typed NUMERIC '1.500000001' and
CAST('01234567-89ab-cdef-0123-456789abcdef' AS UUID) values, including
components of composite keys, key lists, and KEY_RANGE endpoints. Use a
typed NUMERIC literal to retain decimal precision instead of a floating-point
literal. Key components must follow the table's primary-key order.
On a queue-capable Spanner/Omni server, use native Send and Ack mutations:
MUTATE Tasks SEND (key => (42, 'message-1'), payload => b'hello');
MUTATE Tasks SEND (
key => (42, 'message-2'),
payload => b'later',
deliver_time => TIMESTAMP '2030-01-02T03:04:05Z'
);
MUTATE Tasks ACK (key => (42, 'message-1'));
MUTATE Tasks ACK (key => (42, 'message-2'), ignore_not_found => TRUE);Each statement addresses one complete primary key in schema order. Use a scalar
for a single-column key or a tuple for a composite key. Arrays of keys,
KEY_RANGE, ALL, empty keys, and NULL key components are rejected. Argument
names are case-insensitive; duplicates and unknown arguments are errors.
payload is a required literal whose type must match the queue's payload
column. As with other MUTATE operations, supported typed literals and constant
casts can be used; query parameters and server-evaluated expressions are not
supported. The server validates the queue schema and key arity.
deliver_time is an optional non-NULL TIMESTAMP literal. Omitting it requests
immediate delivery. The Go SDK cannot distinguish the zero timestamp
(0001-01-01T00:00:00Z) from an omitted time, so that explicit value is rejected.
ignore_not_found is an optional non-NULL BOOL, defaulting to FALSE: a missing
message normally fails with NOT_FOUND. Setting it to TRUE requests success
when the message is absent. ACK addresses the primary key, not a lease token.
Both operations follow ordinary MUTATE transaction rules: they buffer inside read-write transactions and commit immediately in the default autocommit mode. They respect READONLY and participate in the existing optional savepoint and ABORTED retry journal. To group multiple messages atomically, issue several MUTATE statements in one explicit read-write transaction. Reading or printing a message never automatically runs ACK.
These commands use the current memefish literal syntax and do not require queue DDL parser support, including in MEMEFISH_ONLY mode. Queue service semantics still depend on the server; the local fake-RPC tests verify mutation encoding and transaction replay, not Omni lease or delivery behavior. See the official queue guide.
Example schema
CREATE TABLE MutationTest (PK INT64, Col INT64) PRIMARY KEY(PK);
CREATE TABLE MutationTest2 (PK1 INT64, PK2 STRING(MAX), Col INT64) PRIMARY KEY(PK1, PK2);
Insert a single row with key(1).
MUTATE MutationTest INSERT STRUCT(1 AS PK);
Insert or update four rows with keys(1, "foo", 1, "n", 1, "m", 50, "foobar").
MUTATE MutationTest2 INSERT_OR_UPDATE [STRUCT(1 AS PK1, "foo" AS PK2, 0 AS Col), (1, "n", 1), (1, "m", 3), (50, "foobar", 4)];
You can set commit timestamps using PENDING_COMMIT_TIMESTAMP().
spanner> MUTATE Performances INSERT_OR_UPDATE STRUCT(
1 AS SingerId, 1 AS VenueId,
DATE "2024-12-25" AS EventDate,
PENDING_COMMIT_TIMESTAMP() AS LastUpdateTime);
Query OK, 0 rows affected (1.18 sec)
timestamp: 2024-12-13T02:40:46.253698+09:00
mutation_count: 4
spanner> SELECT * FROM Performances;
+----------+---------+------------+---------+-----------------------------+
| SingerId | VenueId | EventDate | Revenue | LastUpdateTime |
| INT64 | INT64 | DATE | INT64 | TIMESTAMP |
+----------+---------+------------+---------+-----------------------------+
| 1 | 1 | 2024-12-25 | NULL | 2024-12-12T17:40:46.253698Z |
+----------+---------+------------+---------+-----------------------------+
1 rows in set (14.89 msecs)
timestamp: 2024-12-13T02:40:57.15133+09:00
cpu time: 13.35 msecs
rows scanned: 1 rows
deleted rows scanned: 0 rows
optimizer version: 7
optimizer statistics: auto_20241212_12_01_00UTC
Delete all rows in MutationTest table.
MUTATE MutationTest DELETE ALL;
Delete rows with PK (1) in MutationTest table.
MUTATE MutationTest DELETE (1);
Delete a single row with PK (1, "foo") in MutationTest2 table.
MUTATE MutationTest2 DELETE (1, "foo");
Delete two rows with PK (1, "foo", 2, "bar") in MutationTest2 table.
MUTATE MutationTest2 DELETE [(1, "foo"), (2, "bar")];
Delete rows between (1, "a") <= PK < (1, "n") in MutationTest2 table
MUTATE MutationTest2 DELETE KEY_RANGE(start_closed => (1, "a"), end_open => (1, "n"));
Many Cloud Spanner clients don't support query parameters.
If you do not modify the query, you will not be able to execute queries that contain query parameters,
and you will not be able to view the query plan for queries with parameter types STRUCT or ARRAY.
spanner-mycli solves this problem by supporting query parameters.
You can define query parameters using command line option --param or SET commands.
It supports type notation or literal value notation in GoogleSQL.
Parameter names are case-insensitive. SET PARAM MixedCase = 42 and a later
SET PARAM mixedcase = 99 update the same logical parameter and keep the first
stored spelling (MixedCase) for SHOW PARAMS. Binding uses the spelling
actually present in SQL (SELECT @mixedcase binds as mixedcase) and does not
rewrite the statement text. When one statement uses several casings of the same
name, the first SQL occurrence is the RPC parameter name; Spanner matches the
rest case-insensitively. --param uses a map, so differently cased keys have
no retained input order: conflicting values are rejected, and identical aliases
(same kind and memefish SQL() rendering) collapse to one stored spelling
(lexicographically first) before SHOW PARAMS.
Note: They are supported on the best effort basis, and type conversions are not supported.
$ spanner-mycli \
--param='array_type=ARRAY<STRUCT<FirstName STRING, LastName STRING>>' \
--param='array_value=ARRAY<STRUCT<FirstName STRING, LastName STRING>>[("Marc", "Richards"), ("Catalina", "Smith")]'
SHOW PARAMS; lists names, kinds, and types without exposing parameter values.
Use SHOW PARAM <name>; to inspect one parameter, with case-insensitive name matching.
> SHOW PARAMS;
+-------------+------------+------------------------------------------------+
| Param_Name | Param_Kind | Param_Type |
+-------------+------------+------------------------------------------------+
| array_type | TYPE | ARRAY<STRUCT<FirstName STRING, LastName STRING>> |
| array_value | VALUE | ARRAY<STRUCT<FirstName STRING, LastName STRING>> |
+-------------+------------+------------------------------------------------+
2 rows in set (0.00 sec)
> SHOW PARAM array_value;
+-------+------------------------------------------------+
| name | value |
+-------+------------------------------------------------+
| Name | array_value |
| Kind | VALUE |
| Type | ARRAY<STRUCT<FirstName STRING, LastName STRING>> |
| Value | [('Marc', 'Richards'), ('Catalina', 'Smith')] |
+-------+------------------------------------------------+
4 rows in set (0.00 sec)
The value uses compact display notation because its type is shown separately.
Strings remain quoted; STRUCTs use parentheses, arrays use brackets, and NULL
is displayed as NULL. A type-only parameter has the value <unset>. This
notation is for inspection, not a standalone SQL expression. Both commands run
locally. Expressions whose types cannot be determined locally appear as
UNKNOWN in the list.
You can use value query parameters in any statement.
> SELECT * FROM Singers WHERE STRUCT(FirstName, LastName) IN UNNEST(@array_value);
+----------+-----------+----------+------------+------------+
| SingerId | FirstName | LastName | SingerInfo | BirthDate |
+----------+-----------+----------+------------+------------+
| 2 | Catalina | Smith | NULL | 1990-08-17 |
| 1 | Marc | Richards | NULL | 1970-09-03 |
+----------+-----------+----------+------------+------------+
2 rows in set (7.8 msecs)
You can use type query parameters only in EXPLAIN or DESCRIBE without value.
> EXPLAIN SELECT * FROM Singers WHERE STRUCT(FirstName, LastName) IN UNNEST(@array_type);
+-----+----------------------------------------------------------------------------------+
| ID | Query_Execution_Plan |
+-----+----------------------------------------------------------------------------------+
| 0 | Distributed Cross Apply |
| 1 | +- [Input] Create Batch |
| 2 | | +- Compute Struct |
| 3 | | +- Hash Aggregate |
| 4 | | +- Compute |
| 5 | | +- Array Unnest |
| 10 | | +- [Scalar] Array Subquery |
| 11 | | +- Array Unnest |
| 35 | +- [Map] Serialize Result |
| 36 | +- Cross Apply |
| 37 | +- [Input] Batch Scan (Batch: $v14, scan_method: Scalar) |
| 40 | +- [Map] Local Distributed Union |
| *41 | +- Filter Scan (seekable_key_size: 0) |
| 42 | +- Table Scan (Full scan: true, Table: Singers, scan_method: Scalar) |
+-----+----------------------------------------------------------------------------------+
Predicates(identified by ID):
41: Residual Condition: (($FirstName = $batched_v8) AND ($LastName = $batched_v9))
14 rows in set (0.18 sec)
> DESCRIBE SELECT * FROM Singers WHERE STRUCT(FirstName, LastName) IN UNNEST(@array_type);
+-------------+-------------+
| Column_Name | Column_Type |
+-------------+-------------+
| SingerId | INT64 |
| FirstName | STRING |
| LastName | STRING |
| SingerInfo | BYTES |
| BirthDate | DATE |
+-------------+-------------+
5 rows in set (0.17 sec)
You can define type query parameters using SET PARAM param_name type; command.
> SET PARAM string_type STRING;
Empty set (0.00 sec)
> SHOW PARAMS;
+-------------+------------+-------------+
| Param_Name | Param_Kind | Param_Type |
+-------------+------------+-------------+
| string_type | TYPE | STRING |
+-------------+------------+-------------+
Empty set (0.00 sec)
You can define value query parameters using SET PARAM param_name = value; command.
> SET PARAM bytes_value = b"foo";
Empty set (0.00 sec)
> SHOW PARAMS;
+-------------+------------+-------------+
| Param_Name | Param_Kind | Param_Type |
+-------------+------------+-------------+
| bytes_value | VALUE | BYTES |
+-------------+------------+-------------+
Empty set (0.00 sec)
SET PARAM can copy an existing value or a named STRUCT field. Parameter and
field names are case-insensitive; nested field paths are supported.
SET PARAM source = STRUCT<Id INT64, Name STRING>(42, 'Alice');
SET PARAM saved = @source;
SET PARAM id = @source.Id;
SET PARAM source = STRUCT<Id INT64, Name STRING>(99, 'Bob');
SHOW PARAM saved; -- Value: (42, 'Alice')
SELECT @id; -- 42Copies are snapshots taken at assignment, preserving types, typed NULLs, arrays, STRUCTs, BYTES, and INT64 precision. Reassigning or unsetting the source does not change a copy. Failed assignments leave the destination unchanged. Type-only parameters cannot be copied because they have no value.
Only a direct reference (@source) or STRUCT field path (@source.child.Id)
is supported. Expressions combining parameters, such as @id + 1 or ABS(@id),
are rejected; evaluate those with ordinary SQL instead. Assignments run locally
without a database request, and copied parameters follow the existing lifetime
of ordinary user-defined parameters.
spanner-mycli have some partition queries functionality.
You can test whether the query is root-partitionable using TRY PARTITIONED QUERY command.
spanner> TRY PARTITIONED QUERY SELECT * FROM Singers;
+--------------------+
| Root_Partitionable |
+--------------------+
| TRUE |
+--------------------+
1 rows in set (0.78 sec)
spanner> TRY PARTITIONED QUERY SELECT * FROM Singers ORDER BY SingerId;
ERROR: query can't be a partition query: rpc error: code = InvalidArgument desc = Query is not root partitionable since it does not have a DistributedUnion at the root. Please check the conditions for a query to be root-partitionable.
error details: name = Help desc = Conditions for a query to be root-partitionable. url = https://cloud.google.com/spanner/docs/reads#read_data_in_parallel
You can execute partitioned query using RUN PARTITIONED QUERY command.
spanner> RUN PARTITIONED QUERY SELECT * FROM Singers;
+----------+-----------+----------+------------+------------+
| SingerId | FirstName | LastName | SingerInfo | BirthDate |
+----------+-----------+----------+------------+------------+
| 1 | Marc | Richards | NULL | 1970-09-03 |
| 2 | Catalina | Smith | NULL | 1990-08-17 |
| 3 | Alice | Trentor | NULL | 1991-10-02 |
| 4 | Lea | Martin | NULL | 1991-11-09 |
| 5 | David | Lomond | NULL | 1977-01-29 |
+----------+-----------+----------+------------+------------+
5 rows in set from 3 partitions (1.40 sec)
Or you can use SET AUTO_PARTITION_MODE.
spanner> SET AUTO_PARTITION_MODE = TRUE;
Empty set (0.00 sec)
spanner> SELECT * FROM Singers;
+----------+-----------+----------+------------+------------+
| SingerId | FirstName | LastName | SingerInfo | BirthDate |
+----------+-----------+----------+------------+------------+
| 1 | Marc | Richards | NULL | 1970-09-03 |
| 2 | Catalina | Smith | NULL | 1990-08-17 |
| 3 | Alice | Trentor | NULL | 1991-10-02 |
| 4 | Lea | Martin | NULL | 1991-11-09 |
| 5 | David | Lomond | NULL | 1977-01-29 |
+----------+-----------+----------+------------+------------+
5 rows in set from 3 partitions (1.40 sec)
Note: Any stats are not available in partitioned query.
You can set session-level default isolation level and transaction-level isolation level.
spanner> SET DEFAULT_ISOLATION_LEVEL = "REPEATABLE_READ";
spanner> BEGIN ISOLATION LEVEL REPEATABLE READ;
You can enable Data Boost using DATA_BOOST_ENABLED.
spanner> SET DATA_BOOST_ENABLED = TRUE;
In default, the number of worker goroutines for partitioned query is the value of GOMAXPROCS.
You can change it using MAX_PARTITIONED_PARALLELISM.
spanner> SET MAX_PARTITIONED_PARALLELISM = 1;
MAX_PARTITIONS is a nonnegative INT64 hint on PARTITION, TRY PARTITIONED QUERY, RUN PARTITIONED QUERY, and AUTO_PARTITION_MODE. Default 0 means no hint and preserves existing service behavior. It is independent of MAX_PARTITIONED_PARALLELISM and does not truncate returned tokens or guarantee a partition count. RUN PARTITION executes an existing token and does not repartition. The official PartitionOptions API currently ignores this hint.
spanner> SET MAX_PARTITIONS = 10;
Note: Partitioned queries do not support streaming output in the current implementation.
You can export partition tokens using PARTITION. Each token is an experimental complete native envelope (smycli-part/1/<base64url(JSON)>) that carries the database identity, client-side timestamps (issued_at / not_after as RFC3339Nano UTC), and the pinned Go Spanner v1.95.0 BatchReadOnlyTransactionID / Partition binary payloads. Older bare GetPartitionToken values are not replayable. Advertised client validity is one hour, with a one-minute forward-clock tolerance on consume. This is not authentication, remote cleanup, or a Java/JDBC wire format.
PARTITION still opens its own batch read-only transaction. In the pinned SDK, Cleanup/Close are local and do not delete the multiplexed session. Do not treat producer Cleanup as backend resource release.
spanner> PARTITION SELECT * FROM Singers;
+-------------------------------------------------------------------------------------------------+
| Partition_Token |
+-------------------------------------------------------------------------------------------------+
| smycli-part/1/eyJkYXRhYmFzZSI6InByb2plY3RzLy4uLiIsImlzc3VlZF9hdCI6IjIwMjYtMDktMTNUMTI6MDA6MD... |
+-------------------------------------------------------------------------------------------------+
1 rows in set (0.65 sec)
RUN PARTITION '<token>' reconstructs one exported partition on an idle session (no pending, read-write, or read-only transaction, and no manual batch). It executes the captured request as-is: Data Boost, directed-read, priority, SQL, and parameters come from the token, not from the consumer session. Output uses the standard SELECT pipeline (streamed JSONL/CSV, buffered table, SQL export from the inspected SQL or CLI_SQL_TABLE_NAME).
Closing the producer or one consumer does not invalidate sibling tokens. Tokens may appear in history or explicit echo; they are not an extra automatically logged secret.
spanner> RUN PARTITION 'smycli-part/1/...';
+----------+
| SingerId |
+----------+
| 1 |
+----------+
1 rows in set (0.12 sec)
The GEMINI statement supports both Gemini Enterprise Agent Platform (the
Vertex AI backend) and the Gemini API. Gemini Enterprise is the default backend,
uses Application Default Credentials, and uses the connected Spanner project
(CLI_PROJECT) unless CLI_VERTEXAI_PROJECT or vertexai_project overrides it.
vertexai_project = "example-project"To use the Gemini API instead, set GEMINI_API_KEY or GOOGLE_API_KEY in the
environment and select the backend in spanner-mycli. When both variables are
set, the Google Gen AI SDK uses GOOGLE_API_KEY:
SET CLI_GENAI_BACKEND = "GEMINI_API";The canonical backend values are GEMINI_ENTERPRISE and GEMINI_API.
VERTEX_AI remains accepted as an alias for GEMINI_ENTERPRISE.
CLI_VERTEXAI_MODEL selects the model for either backend and defaults to
gemini-3.7-flash. CLI_GENAI_THINKING_LEVEL accepts UNSPECIFIED, MINIMAL,
LOW, MEDIUM, or HIGH. Its default is UNSPECIFIED, which omits the
thinking configuration and lets the selected model use its own default.
The GEMINI statement sends your prompt, the connected database DDL, and its
proto descriptors to the selected Google Gen AI service. Review your
organization's data-handling policy before using it with sensitive schemas.
Built-in Spanner reference docs are always available. Dynamic documentation
lookup via the Developer Knowledge API uses DEVELOPERKNOWLEDGE_API_KEY, or
falls back to GOOGLE_API_KEY. GEMINI_API_KEY is not used for Developer
Knowledge lookup.
The generated query is automatically filled in the prompt.
spanner> GEMINI "Generate query to show all table with table_schema concat by dot if table_schema is not empty string.";
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Column | Value |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| text | SELECT IF(table_schema != '', CONCAT(table_schema, '.', table_name), table_name) AS table_name_with_schema FROM INFORMATION_SCHEMA.TABLES; |
| semanticDescription | This query retrieves all tables and their schemas, concatenating the schema name with the table name if a schema exists, effectively listing all tables with their schema na |
| | mes when applicable. |
| syntaxDescription | The query selects from the INFORMATION_SCHEMA.TABLES view. It uses IF() to check if table_schema is not empty, and if so, concatenates table_schema and table_name with a do |
| | t ('.'). Otherwise, it just returns the table_name. The result is aliased as table_name_with_schema. |
+---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Empty set (5.72 sec)
spanner> SELECT IF(table_schema != '', CONCAT(table_schema, '.', table_name), table_name) AS table_name_with_schema FROM INFORMATION_SCHEMA.TABLES;
spanner-mycli can execute BigQuery SQL with the BIGQUERY client-side statement.
Configure the target project with CLI_BIGQUERY_PROJECT (defaults to CLI_PROJECT when empty).
Optional job settings are available via CLI_BIGQUERY_LOCATION and CLI_BIGQUERY_MAX_BYTES_BILLED.
READONLY sessions allow BIGQUERY scripts only when every statement is a query (SELECT, WITH, or FROM-pipe / parenthesized query); mixed or unrecognized payloads are rejected locally before a BigQuery job is created.
spanner> SET CLI_BIGQUERY_PROJECT = 'my-gcp-project';
Empty set (0.00 sec)
spanner> BIGQUERY SELECT 1 AS n;
+-----+
| n |
| INT |
+-----+
| 1 |
+-----+
1 rows in set (0.42 sec)
spanner-mycli can emit input and output in Markdown.
TODO: More description
$ spanner-mycli -v --set 'CLI_FORMAT=TABLE_DETAIL_COMMENT' --set 'CLI_MARKDOWN_CODEBLOCK=TRUE' --set 'CLI_ECHO_INPUT=TRUE'
spanner> GRAPH FinGraph
-> MATCH (n:Account)
-> RETURN LABELS(n) AS labels, PROPERTY_NAMES(n) AS props, n.id;
```sql
GRAPH FinGraph
MATCH (n:Account)
RETURN LABELS(n) AS labels, PROPERTY_NAMES(n) AS props, n.id;
/*---------------+------------------------------------------+-------+
| labels | props | id |
| ARRAY<STRING> | ARRAY<STRING> | INT64 |
+---------------+------------------------------------------+-------+
| [Account] | [create_time, id, is_blocked, nick_name] | 7 |
| [Account] | [create_time, id, is_blocked, nick_name] | 16 |
| [Account] | [create_time, id, is_blocked, nick_name] | 20 |
+---------------+------------------------------------------+-------+
3 rows in set (6.26 msecs)
timestamp: 2025-02-09T23:28:26.088524+09:00
cpu time: 4.45 msecs
rows scanned: 3 rows
deleted rows scanned: 0 rows
optimizer version: 7
optimizer statistics: auto_20250207_09_19_31UTC
*/
```
spanner-mycli can execute CQL statements of Cassandra interface with CQL client side statement.
# Spanner Cassandra interface doesn't support CQL DDL, so you need to write GoogleSQL DDL
spanner> CREATE TABLE users (
id INT64 OPTIONS (cassandra_type = 'int'),
active BOOL OPTIONS (cassandra_type = 'boolean'),
username STRING(MAX) OPTIONS (cassandra_type = 'text'),
) PRIMARY KEY (id);
Query OK, 0 rows affected (7.88 sec)
spanner> CQL INSERT INTO users (id, active, username) VALUES (1, TRUE, 'John Doe');
Empty set (0.54 sec)
spanner> CQL SELECT * FROM users WHERE id = 1;
+-----+---------+----------+
| id | active | username |
| int | boolean | varchar |
+-----+---------+----------+
| 1 | true | John Doe |
+-----+---------+----------+
1 rows in set (0.55 sec)
Run unit tests.
$ make test
Note: It requires Docker because integration tests using testcontainers.
Or run test except integration tests.
$ make test-quick
In principle, spanner-mycli accepts the same input as spanner-cli, but some compatibility is intentionally not maintained.
BEGIN RW TAG <tag>andBEGIN RO TAG <tag>are no longer supported.- Use
SET TRANSACTION_TAG = "<tag>"andSET STATEMENT_TAG = "<tag>". - Rationale: spanner-cli are broken. cloudspannerecosystem/spanner-cli#132
- Use
<rfc3339_timestamp>inBEGIN RO <rfc3339_timestamp>must be quoted likeBEGIN RO "2025-01-01T00:00:00Z".- Rationale: Raw RFC3339 timestamp is not compatible with GoogleSQL lexical structure and memefish.
\Gis no longer supported.- Use
SET CLI_FORMAT = "VERTICAL". - Rationale:
\Gis not compatible with GoogleSQL lexical structure and memefish.
- Use
\is no longer used for prompt expansions.- Use
%instead. - Rationale:
%works consistently in prompts and TOML configuration files.
- Use
- The default format of
EXPLAINandEXPLAIN ANALYZEhas been changed.
Table output expands tabs to spaces. CLI_TAB_WIDTH sets the display tab width
(default 4):
SET CLI_TAB_WIDTH = 2;
SELECT 'a\tb' AS example;This does not change the export escaping rules of TAB and TSV.