https://github.com/kanjih/go-spnr
ORM for Cloud Spanner to boost your productivity
https://github.com/kanjih/go-spnr
cloud-spanner gcp go golang google-cloud spanner
Last synced: about 1 month ago
JSON representation
ORM for Cloud Spanner to boost your productivity
- Host: GitHub
- URL: https://github.com/kanjih/go-spnr
- Owner: kanjih
- License: mit
- Created: 2021-11-16T23:11:22.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2023-05-08T13:24:09.000Z (almost 3 years ago)
- Last Synced: 2024-06-20T05:11:06.640Z (over 1 year ago)
- Topics: cloud-spanner, gcp, go, golang, google-cloud, spanner
- Language: Go
- Homepage:
- Size: 104 KB
- Stars: 21
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README

ORM for Cloud Spanner to boost your productivity 🚀
[](https://pkg.go.dev/github.com/kanjih/go-spnr/v2)
[](https://github.com/kanjih/go-spnr/actions)
## Example 🔧
```go
package main
import (
"cloud.google.com/go/spanner"
"context"
"github.com/kanjih/go-spnr"
)
type Singer struct {
// spnr supports 2 types of tags.
// - spanner: spanner column name
// - pk: primary key order
SingerID string `spanner:"SingerId" pk:"1"`
Name string `spanner:"Name"`
}
func main() {
ctx := context.Background()
client, _ := spanner.NewClient(ctx, "projects/{project_id}/instances/{instance_id}/databases/{database_id}")
// initialize
singerStore := spnr.New("Singers") // specify table name
// save record (spnr supports both Mutation API & DML!)
singerStore.ApplyInsertOrUpdate(ctx, client, &Singer{SingerID: "a", Name: "Alice"})
// fetch record
var singer Singer
singerStore.Reader(ctx, client.Single()).FindOne(spanner.Key{"a"}, &singer)
// fetch record using raw query
var singers []Singer
query := "select * from Singers where SingerId=@singerId"
params := map[string]any{"singerId": "a"}
singerStore.Reader(ctx, client.Single()).Query(query, params, &singers)
}
```
## Features
- Supports both **Mutation API** & **DML**
- Supports code generation to map records
- Supports raw SQLs for complicated cases
spnr is designed ...
- 🙆♂️ for reducing boliderplate codes (i.e. mapping selected records to struct or write simple insert/update/delete operations)
- 🙅♀️ not for hiding queries executed in background (spnr doesn't support abstractions for complicated operations)
## Table of contents
- [Installation](#installation)
- spnr APIs
- [Read operations](#read-operations)
- [Mutation API](#mutation-api)
- [DML](#dml)
- [Embedding](#embedding)
- [Code generation](#code-generation)
- [Helper functions](#helper-functions)
## Installation
```
go get github.com/kanjih/go-spnr/v2
```
\* v2 requires go 1.18 or later. If you use previous go versions please use v1.
## Read operations
spnr provides the following types of read operations 💪
1. Select records using primary keys
2. Select one column using primary keys
3. Select records using query
4. Select one value using query
### 1. Select records using primary keys
```go
var singer Singer
singerStore.Reader(ctx, tx).FindOne(spanner.Key{"a"}, &singer)
var singers []Singer
keys := spanner.KeySetFromKeys(spanner.Key{"a"}, spanner.Key{"b"})
singerStore.Reader(ctx, tx).FindAll(keys, &singers)
```
#### 📝 Note
`tx` is the transaction object. You can get it by calling `spanner.Client.ReadOnly(ReadWrite)Transaction`, or `spanner.Client.Single` method.
### 2. Select one column using primary keys
```go
var name string
singerStore.Reader(ctx, tx).GetColumn(spanner.Key{"a"}, "Name", &name)
var names []string
keys := spanner.KeySetFromKeys(spanner.Key{"a"}, spanner.Key{"b"})
singerStore.Reader(ctx, tx).GetColumnAll(keys, "Name", &names)
```
#### In the case you want to fetch multiple columns
Making temporal struct to map columns is the best solution.
```go
type cols struct {
Name string `spanner:"Name"`
Score spanner.NullInt64 `spanner:"Score"`
}
var res cols
singerStore.Reader(ctx, tx).FindOne(spanner.Key{"1"}, &res)
```
### 3. Select records using query
```go
var singer Singer
query := "select * from `Singers` where SingerId=@singerId"
params := map[string]any{"singerId": "a"}
singerStore.Reader(ctx, tx).QueryOne(query, params, &singer)
var singers []Singer
query = "select * from Singers"
singerStore.Reader(ctx, tx).Query(query, nil, &singers)
```
### 4. Select one value using query
```go
var cnt int64
query := "select count(*) as cnt from Singers"
singerStore.Reader(ctx, tx).QueryValue(query, nil, &cnt)
```
### * Notes
- `FindOne`, `GetColumn` method uses `ReadRow` method of `spanner.ReadWrite(ReadOnly)Transaction`.
- `FindAll`, `GetColumnAll` method uses `Read` method.
- `QueryOne`, `Query` Method uses `Query` method.
## Mutation API
Executing mutation API using spnr is badly simple! Here's the example 👇
```go
singer := &Singer{SingerID: "a", Name: "Alice"}
singers := []Singer{{SingerID: "b", Name: "Bob"}, {SingerID: "c", Name: "Carol"}}
singerStore := spnr.New("Singers") // specify table name
singerStore.InsertOrUpdate(tx, singer) // Insert or update
singerStore.InsertOrUpdate(tx, &singers) // Insert or update multiple records
singerStore.Update(tx, singer) // Update
singerStore.Update(tx, &singers) // Update multple records
singerStore.Delete(tx, singer) // Delete
singerStore.Delete(tx, &singers) // Delete multiple records
```
Don't want to use in transaction? You can use `ApplyXXX`.
```go
singerStore.ApplyInsertOrUpdate(ctx, client, singer) // client is spanner.Dataclient
singerStore.ApplyDelete(ctx, client, &singers)
```
## DML
spnr parses struct then build DML 💪
```go
singer := &Singer{SingerID: "a", Name: "Alice"}
singers := []Singer{{SingerID: "b", Name: "Bob"}, {SingerID: "c", Name: "Carol"}}
singerStore := spnr.NewDML("Singers") // specify table name
singerStore.Insert(ctx, tx, singer)
// -> INSERT INTO `Singers` (`SingerId`, `Name`) VALUES (@SingerId, @Name)
singerStore.Insert(ctx, tx, &singers)
// -> INSERT INTO `Singers` (`SingerId`, `Name`) VALUES (@SingerId_0, @Name_0), (@SingerId_1, @Name_1)
singerStore.Update(ctx, tx, singer)
// -> UPDATE `Singers` SET `Name`=@Name WHERE `SingerId`=@w_SingerId
singerStore.Update(ctx, tx, &singers)
// -> UPDATE `Singers` SET `Name`=@Name WHERE `SingerId`=@w_SingerId
// -> UPDATE `Singers` SET `Name`=@Name WHERE `SingerId`=@w_SingerId
singerStore.Delete(ctx, tx, singer)
// -> DELETE FROM `Singers` WHERE `SingerId`=@w_SingerId
```
### Want to use raw SQL?
You don't need spnr in this case! Plain spanner SDK is enough.
```go
sql := "UPDATE `Singers` SET `Name` = xx WHERE `Id` = @Id"
params := map[string]any
spannerClient.Update(tx, spanner.Statement{SQL: sql, Params: params})
```
## Embedding
spnr is also designed to use with embedding.
You can make structs to manipulate records for each table & can add any methods you want.
```go
type SingerStore struct {
spnr.DML // use spnr.Mutation for mutation API
}
func NewSingerStore() *SingerStore {
return &SingerStore{DML: *spnr.NewDML("Singers")}
}
// Any methods you want to add
func (s *SingerStore) GetCount(ctx context.Context, tx spnr.Transaction, cnt any) error {
query := "select count(*) as cnt from Singers"
return s.Reader(ctx, tx).Query(query, nil, &cnt)
}
func useSingerStore(ctx context.Context, client *spanner.Client) {
singerStore := NewSingerStore()
client.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {
// You can use all operations that spnr.DML has
singerStore.Insert(ctx, tx, &Singer{SingerID: "a", Name: "Alice"})
var singer Singer
singerStore.Reader(ctx, tx).FindOne(spanner.Key{"a"}, &singer)
// And you can use the methods you added !!
var cnt int
singerStore.GetCount(ctx, tx, &cnt)
return nil
})
}
```
## Code generation
Tired to write struct code to map records for every table?
Don't worry! spnr provides code generation 🚀
```sh
go install github.com/kanjih/go-spnr/cmd/spnr@latest
spnr build -p {PROJECT_ID} -i {INSTANCE_ID} -d {DATABASE_ID} -n {PACKAGE_NAME} -o {OUTPUT_DIR}
```
## Helper functions
spnr provides some helper functions to reduce boilerplates.
- **`NewNullXXX`**
- `spanner.NullString{StringVal: "a", Valid: true}` can be `spnr.NewNullString("a")`
- **`ToKeySets`**
- You can convert slice to keysets using `spnr.ToKeySets([]string{"a", "b"})`
Love reporting issues!
[godev-image]: https://pkg.go.dev/badge/github.com/kanjih/go-spnr
[godev-url]: https://pkg.go.dev/github.com/kanjih/go-spnr