{"id":13721486,"url":"https://github.com/gcpug/handy-spanner","last_synced_at":"2026-01-26T09:05:56.709Z","repository":{"id":36005597,"uuid":"212796897","full_name":"gcpug/handy-spanner","owner":"gcpug","description":"An unofficial emulator of Cloud Spanner backed by sqlite3","archived":false,"fork":false,"pushed_at":"2025-02-12T17:19:06.000Z","size":671,"stargazers_count":158,"open_issues_count":4,"forks_count":24,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-07T13:37:10.777Z","etag":null,"topics":["google-cloud-spanner"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gcpug.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-10-04T11:12:03.000Z","updated_at":"2025-05-02T02:53:33.000Z","dependencies_parsed_at":"2022-08-02T20:01:01.903Z","dependency_job_id":"707e6b67-91d7-40cd-979d-10474b239da3","html_url":"https://github.com/gcpug/handy-spanner","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/gcpug/handy-spanner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gcpug%2Fhandy-spanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gcpug%2Fhandy-spanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gcpug%2Fhandy-spanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gcpug%2Fhandy-spanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gcpug","download_url":"https://codeload.github.com/gcpug/handy-spanner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gcpug%2Fhandy-spanner/sbom","scorecard":{"id":420798,"data":{"date":"2025-08-11","repo":{"name":"github.com/gcpug/handy-spanner","commit":"be02b15c5d7e51b88c106a46c1e6274d715920ef"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.1,"checks":[{"name":"Code-Review","score":9,"reason":"Found 9/10 approved changesets -- score normalized to 9","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":2,"reason":"3 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: containerImage not pinned by hash: Dockerfile:1","Warn: containerImage not pinned by hash: Dockerfile:16: pin your Docker image by updating alpine:3.16.1 to alpine:3.16.1@sha256:7580ece7963bfa863801466c0a488f11c86f85d9988051a9f9c68cb27f6b7872","Info:   0 out of   2 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw","Warn: Project is vulnerable to: GO-2025-3488 / GHSA-6v2p-p543-phr9"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T01:08:45.698Z","repository_id":36005597,"created_at":"2025-08-19T01:08:45.698Z","updated_at":"2025-08-19T01:08:45.698Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28771633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T08:38:24.014Z","status":"ssl_error","status_checked_at":"2026-01-26T08:38:22.080Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["google-cloud-spanner"],"created_at":"2024-08-03T01:01:17.695Z","updated_at":"2026-01-26T09:05:56.703Z","avatar_url":"https://github.com/gcpug.png","language":"Go","funding_links":[],"categories":["Tools"],"sub_categories":["ORM"],"readme":"# An emulator for Cloud Spanner\n\n![Status: Maintenance](https://img.shields.io/badge/status-maintenance-yellow.svg)\n\n## ⚠️ Maintenance Mode\n\n\u003e **Note:**\n\u003e Active development of `handy-spanner` has ended due to the release of the [official Google Cloud Spanner Emulator](https://cloud.google.com/spanner/docs/emulator).\n\u003e\n\u003e This project is currently in **maintenance mode**. No new features will be added. We will only perform essential maintenance, such as updating dependencies.\n\n---\n\n## Install\n\n```\ngo get github.com/gcpug/handy-spanner/cmd/handy-spanner\n```\n\nNOTE: If you want to use some features (e.g. array literal `[]` in DML), require \"json1\" build tag.\nThe Spanner emulator uses sqlite3 internally. You may need to build go-sqlite3 explicitly.\nIt also requires cgo to use sqlite3.\n\n```\ngo get -u github.com/mattn/go-sqlite3\ngo install github.com/mattn/go-sqlite3\n```\n\n## Usage\n\n### Run as an independent process\n\n```\n./handy-spanner\n```\n\nor\n\n```\ndocker run --rm -it -p 9999:9999 handy-spanner\n```\n\nIt runs a hand-spanner server as a process. It serves spanner gRPC server by port 9999 by default.\n\n#### Access to the server\n\nThe google-cloud-go, the official Spanner SDK, supports to access an emulator server.\nSet the address to an emulator server to environment variable `SPANNER_EMULATOR_HOST`, then google-cloud-go transparently use the server in the client.\n\nSo if you want to replace spanner server with the handy-spanner you run, just do:\n\n```\nexport SPANNER_EMULATOR_HOST=localhost:9999\n```\n\n#### Schema setup\n\nThe handy-spanner server has no databases nor tables by default. You need to create them by yourself. You can prepare them at startup.\n\nhandy-spanner accepts some command-line arguments to prepare schema.\n\n* `schema`: Path to a DDL file which handy-spanner creates at startup\n* `project`: The project the DDL is applied.\n* `instance`: The instance the DDL is applied.\n* `database`: The database the DDL is applied.\n\nIf you prepare schema at startup, these 4 arguments are required.\n\n#### Implicit creation of databases\n\nWithout schema setup, databases are created automatically by your database access.\n\nhandy-spanner creates a database when a session to the database is created. In detail, when `CreateSession` or `BatchCreateSessions` is called, the accessed database is created.\n\n#### Database operations\n\nYou can also operate databases by your applications. If you need additional databases or database alterations, you need to follow this section.\n\nAs Cloud Spanner supports, there are dedicated gRPC service for instance and database operations for Spanner.\n\n* Instance operations\n  * [Document](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1)\n  * [Protobuf](https://github.com/googleapis/googleapis/blob/master/google/spanner/admin/instance/v1/spanner_instance_admin.proto)\n* Database operations\n  * [Document](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.database.v1)\n  * [Protobuf](https://github.com/googleapis/googleapis/blob/master/google/spanner/admin/database/v1/spanner_database_admin.proto)\n\nhandy-spanner also supports these gRPC services so that you can operate databases. The gRPC services are provided in the same address to the normal gRPC service. It means it is provided in `localhost:9999` by default.\n\nIt seems most clients for each language for these gRPC services provided Google Cloud Platform dont support `SPANNER_EMULATOR_HOST` to connect an emulator server, so you need to setup a client to connect an emulator server explicitly. For Go, you can refer [examples](./fake/example_test.go) to connect an emulator server.\n\n### Run as a buillt-in server in Go\n\nIf you use a handy-spanner server in tests in Go, it's easier to run it in a process.\nSee an [example](https://github.com/gcpug/handy-spanner/blob/master/fake/example_test.go) for the details.\n\nNote that the tests highly depend on handhy-spanner, which means you cannot switch the backend depending on the situation. If you want to test on both Cloud Spanner and handy-spanner, it's better to use a handy-spanner server as an independent process.\n\n#### Database operations\n\nhandy-spanner provides utility functions to operate databases. For the detail, please refer [examples](./fake/example_test.go).\n\n* `ParseAndApplyDDL`\n  * You can pass `io.Reader` as a schema file to apply DDL to a database.\n* `ApplyDDL`\n  * You can pass `ast.DDL` as an already parsed definition to apply DDL to a database.\n\nYou can also operate databases or instances as Cloud Spanner supports. Please also refer _Run as an independent process_ section.\n\n## Can and Cannot\n\n### Supported features\n\n* Read\n   * Keys and KeyRange as KeySet\n   * Secondary index\n   * STORING columns for secondary index\n   * Respect column orders for index\n* Query\n   * Select result set by column name and *\n   * Most operators in WHERE clause: IN, BETWEEN, IS NULL\n   * Conditions in WHERE clause: =, !=, \u003e, \u003c, AND, OR\n   * Order By keyword with ASC, DESC\n   * Group By and Having statement\n   * LIMIT OFFSET\n   * SELECT alias\n   * Query Parameters\n   * Literals\n   * JOINs: COMMMA, CROSS, INNER, LEFT/RIGHT/FULL OUTER\n      * FULL OUTER has some limitations\n   * Subquery\n   * SET operations: UNION, INTERSECT, EXCEPT\n   * UNNEST: IN UNNEST, FROM UNNEST\n   * Functions (partially)\n   * Arithmetic operations\n* Mutation\n   * All mutation types: Insert, Update, InsertOrUpdate, Replace, Delete\n   * Commit timestamp\n* Transaction\n   * Isolation level: SERIALIZABLE\n* DML\n   * Insert, Update, Delete\n   * Batch DML\n* DDL\n   * CreateTable, CreateIndex only\n   * Respect INTERLEAVE\n* Data Types\n   * Int, Float, String, Bool, Byte, Date, Timestamp, Array\u003cAny\u003e, Struct\n* [Information Schema](https://cloud.google.com/spanner/docs/information-schema)\n   * partially supported\n\n### Not supported features\n\n* Transaction\n   * Timestamp bound read\n* Query\n   * Strict type checking\n   * More functions\n   * Partionan Query\n   * EXCEPT ALL and INTERSECT ALL (because of sqlite)\n   * FULL OUTER JOIN\n     * Not support table alias. Use `*` for now\n     * Not support ON condition. Use USING condition for now\n   * Merging INT64 and FLOAT64 in SET operations\n   * Array operations\n* DDL\n   * Alter Table, Drop Table, Drop Index\n   * Database management\n   * Long running operations\n* Replace\n   * wrong behavior on conflict\n* [Change Stream Schema](https://cloud.google.com/spanner/docs/change-streams)\n   * only can parse in [Database operations](#Database-operations) with `schema` flag.\n\n## Implementation\n\n### Transaction simulation\n\nhandy-spanner uses sqlite3 in [Shared-Cache Mode](https://www.sqlite.org/sharedcache.html). There is a characteristic in the trasactions.\n\n* Only one transaction can hold write lock per database to write database tables.\n    * Other transactions still can hold read lock.\n* Write transaction holds write lock against database tables while writing the tables.\n    * Other read transactions cannot read the table while locked\n* Read transaction holds read lock against database tables while reading the tables.\n    * Other read transactions can read the table by holding read lock\n    * Write transaction cannot write the table while read-locked\n\nIf we simply use the transactions, dead lock should happen in read and write locks. To simulate spanner transactions correctly as possible, handy-spanner manages sqlite3 transactions inside.\n\n* Each spanner transaction starts own sqlite3 transaction.\n* Only one spanner transaction can hold write lock per database.\n* While a transaction holds write lock, other spanner transactions cannot newly get read or write lock.\n* When write transaction tries to write a table, it forces transactions that hold read lock to the table to release the lock.\n   * The transactions become \"aborted\"\n* The aborted transactions are expected to be retried by the client.\n\n![abort](img/abort1.png) ![abort](img/abort2.png)\n\n### DML\n\nBecause of transaction limitations, DML also has limitations.\n\nWhen a transaction(A) updates a table, other transactions cannot read/write the table until the transaction(A) commits. This limitation may become an inconsistency to the Cloud Spanner. Other limitations are same to mutations with commit.\n\n![block](img/read_block.png)\n\n## Feature Request and Bug Report\n\nFeature requests and any bug reports are welcome.\n\nThere are many things to implement features and make better compatibility to Cloud Spanner. Now I'm prioritizing features by on-demand from users and me. Please create an issue if you find lack of a feature to use handy-spanner or imcompatibility to Cloud Spanner.\n\n#### Feature request\n\nPlease describe what feature you want. It's the best to refer a Cloud Spanner's document for the feature. If the feature is partiality implemented or wrong behavior you expected, please also see a Bug Report section.\n\n#### Bug report\n\nPlease describe a reproducable conditions. If you describe more information about the bug, it makes more easir to fix the bug.\n\n* Schema\n* Initial Data\n* Query or any requests you run\n* Actual behavior\n* Expected behavior\n* Document in Cloud Spanner\n\n## Copyright\n\n* Author: Masahiro Sano ([@kazegusuri](https://github.com/kazegusuri))\n* Copyright: 2019 Masahiro Sano\n* License: Apache License, Version 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgcpug%2Fhandy-spanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgcpug%2Fhandy-spanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgcpug%2Fhandy-spanner/lists"}