spnr

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 24, 2022 License: MIT Imports: 14 Imported by: 1

README ΒΆ

ORM for Cloud Spanner to boost your productivity πŸš€

GoDev

Example πŸ”§

package main

import (
	"cloud.google.com/go/spanner"
	"context"
	"fmt"
	"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.Context, client *spanner.Client) {
	// 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]interface{}{"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

go get github.com/kanjih/go-spnr

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

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

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.

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

var singer Singer
query := "select * from `Singers` where SingerId=@singerId"
params := map[string]interface{}{"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

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 πŸ‘‡

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.

singerStore.ApplyInsertOrUpdate(ctx, client, singer) // client is spanner.Dataclient
singerStore.ApplyDelete(ctx, client, &singers)

DML

spnr parses struct then build DML πŸ’ͺ

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.

sql := "UPDATE `Singers` SET `Name` = xx WHERE `Id` = @Id"
params := map[string]interface{}
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.

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 interface{}) 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 πŸš€

go get github.com/kanjih/go-spnr/cmd/spnr
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!

Documentation ΒΆ

Overview ΒΆ

Package spnr provides the orm for Cloud Spanner.

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	// ErrNotFound is returned when a read operation cannot find any records unexpectedly.
	ErrNotFound = errors.New("record not found")
	// ErrNotFound is returned  when a read operation found multiple records unexpectedly.
	ErrMoreThanOneRecordFound = errors.New("more than one record found")
)

Functions ΒΆ

func NewNullBool ΒΆ

func NewNullBool(b bool) spanner.NullBool

NewNullBool initializes spanner.NullBool setting Valid as true

func NewNullDate ΒΆ

func NewNullDate(d civil.Date) spanner.NullDate

NewNullDate initializes spanner.NullDate setting Valid as true

func NewNullInt64 ΒΆ

func NewNullInt64(val int64) spanner.NullInt64

NewNullInt64 initializes spanner.NullInt64 setting Valid as true

func NewNullNumeric ΒΆ

func NewNullNumeric(a, b int64) spanner.NullNumeric

NewNullNumeric initializes spanner.NullNumeric setting Valid as true

func NewNullString ΒΆ

func NewNullString(str string) spanner.NullString

NewNullString initializes spanner.NullString setting Valid as true

func NewNullTime ΒΆ

func NewNullTime(t time.Time) spanner.NullTime

NewNullTime initializes spanner.NullTime setting Valid as true

func ToAllColumnNames ΒΆ

func ToAllColumnNames(target interface{}) string

ToAllColumnNames receives struct and returns the fields that the passed struct has. This method is useful when you build query to select all the fields. Instead of use *(wildcard), you can specify all of the columns using this method. Then you can avoid the risk that failing to map record to struct caused by the mismatch of an order of columns in spanner table and fields in struct.

func ToKeySets ΒΆ

func ToKeySets(target interface{}) spanner.KeySet

ToKeySets convert any slice to spanner.KeySet

Types ΒΆ

type DML ΒΆ

type DML struct {
	// contains filtered or unexported fields
}

DML offers ORM with DML. It also contains read operations (call Reader method.)

func NewDML ΒΆ

func NewDML(tableName string) *DML

NewDML initializes ORM with DML. It also contains read operations (call Reader method of DML.) If you want to use Mutation API, use New() or NewMutation() instead.

func NewDMLWithOptions ΒΆ

func NewDMLWithOptions(tableName string, op *Options) *DML

NewDMLWithOptions initializes DML with options. Check Options for the available options.

func (*DML) Delete ΒΆ

func (d *DML) Delete(ctx context.Context, tx *spanner.ReadWriteTransaction, target interface{}) (rowCount int64, err error)

Delete build and execute delete statement from the passed struct. You can pass either a struct or a slice of structs to target. If you pass a slice of structs, this method will build statement which deletes multiple records in one statement like the following.

DELETE FROM `T` WHERE (`COL1` = 'a' AND `COL2` = 'b') OR (`COL1` = 'c' AND `COL2` = 'd');

func (*DML) GetTableName ΒΆ

func (d *DML) GetTableName() string

GetTableName returns table name

func (*DML) Insert ΒΆ

func (d *DML) Insert(ctx context.Context, tx *spanner.ReadWriteTransaction, target interface{}) (rowCount int64, err error)

Insert build and execute insert statement from the passed struct. You can pass either a struct or a slice of struct to target. If you pass a slice of struct, this method will build a statement which insert multiple records in one statement like the following

INSERT INTO `TableName` (`Column1`, `Column2`) VALUES ('a', 'b'), ('c', 'd'), ...;

func (*DML) Reader ΒΆ

func (d *DML) Reader(ctx context.Context, tx Transaction) *Reader

Reader returns Reader struct to call read operations.

func (*DML) Update ΒΆ

func (d *DML) Update(ctx context.Context, tx *spanner.ReadWriteTransaction, target interface{}) (rowCount int64, err error)

Update build and execute update statement from the passed struct. You can pass either a struct or slice of struct to target. If you pass a slice of struct, this method will call update statement in for loop.

func (*DML) UpdateColumns ΒΆ

func (d *DML) UpdateColumns(ctx context.Context, tx *spanner.ReadWriteTransaction, columns []string, target interface{}) (rowCount int64, err error)

UpdateColumns build and execute update statement from the passed column names and struct. You can specify the columns to update. Also, you can pass either a struct or slice of struct to target. If you pass a slice of struct, this method will call update statement in for loop.

type Mutation ΒΆ

type Mutation struct {
	// contains filtered or unexported fields
}

DML offers ORM with Mutation API. It also contains read operations (call Reader method.)

func New ΒΆ

func New(tableName string) *Mutation

New is alias for NewMutation.

func NewMutation ΒΆ

func NewMutation(tableName string) *Mutation

NewMutation initializes ORM with Mutation API. It also contains read operations (call Reader method of Mutation.) If you want to use DML, use NewDML() instead.

func NewMutationWithOptions ΒΆ

func NewMutationWithOptions(tableName string, op *Options) *Mutation

NewDMLWithOptions initializes Mutation with options. Check Options for the available options.

func (*Mutation) ApplyDelete ΒΆ

func (m *Mutation) ApplyDelete(ctx context.Context, client *spanner.Client, target interface{}) (time.Time, error)

ApplyDelete is basically same as Delete, but it doesn't require transaction. This method directly calls mutation API without transaction by calling spanner.Client.Apply method.

func (*Mutation) ApplyInsertOrUpdate ΒΆ

func (m *Mutation) ApplyInsertOrUpdate(ctx context.Context, client *spanner.Client, target interface{}) (time.Time, error)

ApplyInsertOrUpdate is basically same as InsertOrUpdate, but it doesn't require transaction. This method directly calls mutation API without transaction by calling spanner.Client.Apply method. If you want to insert or update only the specified columns, use ApplyInsertOrUpdateColumns instead.

func (*Mutation) ApplyInsertOrUpdateColumns ΒΆ

func (m *Mutation) ApplyInsertOrUpdateColumns(ctx context.Context, client *spanner.Client, columns []string, target interface{}) (time.Time, error)

ApplyInsertOrUpdateColumns is basically same as InsertOrUpdateColumns, but it doesn't require transaction. This method directly calls mutation API without transaction by calling spanner.Client.Apply method.

func (*Mutation) ApplyUpdate ΒΆ

func (m *Mutation) ApplyUpdate(ctx context.Context, client *spanner.Client, target interface{}) (time.Time, error)

ApplyUpdate is basically same as Update, but it doesn't require transaction. This method directly calls mutation API without transaction by calling spanner.Client.Apply method. If you want to update only the specified columns, use ApplyUpdateColumns instead.

func (*Mutation) ApplyUpdateColumns ΒΆ

func (m *Mutation) ApplyUpdateColumns(ctx context.Context, client *spanner.Client, columns []string, target interface{}) (time.Time, error)

ApplyUpdateColumns is basically same as UpdateColumns, but it doesn't require transaction. This method directly calls mutation API without transaction by calling spanner.Client.Apply method.

func (*Mutation) Delete ΒΆ

func (m *Mutation) Delete(tx *spanner.ReadWriteTransaction, target interface{}) error

Delete build and execute delete operation using mutation API. You can pass either a struct or a slice of structs. If you pass a slice of structs, this method will build a mutation for each struct. This method requires spanner.ReadWriteTransaction, and will call spanner.ReadWriteTransaction.BufferWrite to save the mutation to transaction.

func (*Mutation) GetTableName ΒΆ

func (m *Mutation) GetTableName() string

GetTableName returns table name

func (*Mutation) InsertOrUpdate ΒΆ

func (m *Mutation) InsertOrUpdate(tx *spanner.ReadWriteTransaction, target interface{}) error

InsertOrUpdate build and execute insert_or_update operation using mutation API. You can pass either a struct or a slice of structs. If you pass a slice of structs, this method will call multiple mutations for each struct. This method requires spanner.ReadWriteTransaction, and will call spanner.ReadWriteTransaction.BufferWrite to save the mutation to transaction. If you want to insert or update only the specified columns, use InsertOrUpdateColumns instead.

func (*Mutation) InsertOrUpdateColumns ΒΆ

func (m *Mutation) InsertOrUpdateColumns(tx *spanner.ReadWriteTransaction, columns []string, target interface{}) error

InsertOrUpdateColumns build and execute insert_or_update operation for specified columns using mutation API. You can pass either a struct or a slice of structs to target. If you pass a slice of structs, this method will build a mutation for each struct. This method requires spanner.ReadWriteTransaction, and will call spanner.ReadWriteTransaction.BufferWrite to save the mutation to transaction.

func (*Mutation) Reader ΒΆ

func (m *Mutation) Reader(ctx context.Context, tx Transaction) *Reader

Reader returns Reader struct to call read operations.

func (*Mutation) Update ΒΆ

func (m *Mutation) Update(tx *spanner.ReadWriteTransaction, target interface{}) error

Update build and execute update operation using mutation API. You can pass either a struct or a slice of structs. If you pass a slice of structs, this method will call multiple mutations for each struct. This method requires spanner.ReadWriteTransaction, and will call spanner.ReadWriteTransaction.BufferWrite to save the mutation to transaction. If you want to update only the specified columns, use UpdateColumns instead.

func (*Mutation) UpdateColumns ΒΆ

func (m *Mutation) UpdateColumns(tx *spanner.ReadWriteTransaction, columns []string, target interface{}) error

UpdateColumns build and execute update operation for specified columns using mutation API. You can pass either a struct or a slice of structs to target. If you pass a slice of structs, this method will build a mutation for each struct. This method requires spanner.ReadWriteTransaction, and will call spanner.ReadWriteTransaction.BufferWrite to save the mutation to transaction.

type Options ΒΆ

type Options struct {
	Logger     logger
	LogEnabled bool
}

Options is for specifying the options for spnr.Mutation and spnr.DML.

type Reader ΒΆ

type Reader struct {
	// contains filtered or unexported fields
}

Reader executes read operations.

func (*Reader) FindAll ΒΆ

func (r *Reader) FindAll(keys spanner.KeySet, target interface{}) error

FindAll fetches records by specified a set of primary keys, and map the records into the passed pointer of slice of structs.

func (*Reader) FindOne ΒΆ

func (r *Reader) FindOne(key spanner.Key, target interface{}) error

FindOne fetches a record by specified primary key, and map the record into the passed pointer of struct.

func (*Reader) GetColumn ΒΆ

func (r *Reader) GetColumn(key spanner.Key, column string, target interface{}) error

GetColumn fetches the specified column by specified primary key, and map the column into the passed pointer of value.

Caution:

It maps fetched column to the passed pointer by just calling spanner.Row.Columns method. So the type of passed value to map should be compatible to this method. For example if you fetch an INT64 column from spanner, you need to map this value to int64, not int.

func (*Reader) GetColumnAll ΒΆ

func (r *Reader) GetColumnAll(keys spanner.KeySet, column string, target interface{}) error

GetColumn fetches the specified column for the records that matches specified set of primary keys, and map the column into the passed pointer of a slice of values. Please see the caution commented in GetColumn to check type compatibility.

func (*Reader) Query ΒΆ

func (r *Reader) Query(sql string, params map[string]interface{}, target interface{}) error

Query fetches records by calling specified query, and map the records into the passed pointer of a slice of struct.

func (*Reader) QueryOne ΒΆ

func (r *Reader) QueryOne(sql string, params map[string]interface{}, target interface{}) error

QueryOne fetches a record by calling specified query, and map the record into the passed pointer of struct.

Errors:

If no records are found, this method will return ErrNotFound. If multiple records are found, this method will return ErrMoreThanOneRecordFound.

If you don't need to fetch all columns but only needs one column, use QueryValue instead. If you don't need to fetch all columns but only needs some columns, please make a temporal struct to map the columns.

func (*Reader) QueryValue ΒΆ

func (r *Reader) QueryValue(sql string, params map[string]interface{}, target interface{}) error

QueryValue fetches one value by calling specified query, and map the value into the passed pointer of value.

Errors:

If no records are found, this method will return ErrNotFound. If multiple records are found, this method will return ErrMoreThanOneRecordFound.

Example:

var cnt int64
QueryValue("select count(*) as cnt from Singers", nil, &cnt)

func (*Reader) QueryValues ΒΆ

func (r *Reader) QueryValues(sql string, params map[string]interface{}, target interface{}) error

QueryValues fetches each value of multiple records by calling specified query, and map the values into the passed pointer of a slice of struct.

Example:

var names []string
QueryValue("select Name from Singers", nil, &names)

type Transaction ΒΆ

type Transaction interface {
	Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator
	ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error)
	Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator
}

Transaction is the interface for spanner.ReadOnlyTransaction and spanner.ReadWriteTransaction

Directories ΒΆ

Path Synopsis
cmd
handlers
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL