sqlgen

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: May 5, 2024 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Example (TagOverride)

You can override how values are serialized via struct tags. The following tags are available:

string (encoding.TextMarshaler, encoding.TextUnmarshaler), binary (encoding.BinaryMarshaler, encoding.BinaryUnmarshaler, gogoproto.Marshaler, gogoproto.Unmarshaler), json (json.Marshaler, json.Unmarshaler).

package main

import (
	"context"
	"fmt"
	"net"
	"reflect"

	"github.com/base-go/baseql/internal/testfixtures"
	"github.com/base-go/baseql/sqlgen"
	uuid "github.com/satori/go.uuid"
)

type UUID uuid.UUID

func (u UUID) MarshalBinary() ([]byte, error) {
	return u[:], nil
}

func (u *UUID) UnmarshalBinary(b []byte) error {
	copy(u[:], b)
	return nil
}

// You can override how values are serialized via struct tags. The following tags are available:
//
// string (encoding.TextMarshaler, encoding.TextUnmarshaler), binary (encoding.BinaryMarshaler,
// encoding.BinaryUnmarshaler, gogoproto.Marshaler, gogoproto.Unmarshaler), json (json.Marshaler,
// json.Unmarshaler).
func main() {
	testDb, _ := testfixtures.NewTestDatabase()
	defer testDb.Close()

	_, err := testDb.Exec(`
		CREATE TABLE users (
			uuid          BINARY(16) PRIMARY KEY,
			name          VARCHAR(255),
			ip            VARCHAR(255),
			configuration BLOB
		)
	`)
	if err != nil {
		panic(err)
	}

	ctx := context.TODO()

	type User struct {
		UUID          UUID `sql:"uuid,primary,binary"`
		Name          string
		IP            net.IP                 `sql:"ip,string"`
		Configuration map[string]interface{} `sql:"configuration,json"`
	}

	schema := sqlgen.NewSchema()
	schema.MustRegisterType("users", sqlgen.UniqueId, User{})

	db := sqlgen.NewDB(testDb.DB, schema)
	uuid := uuid.NewV4()

	initialUser := &User{
		UUID: UUID(uuid),             // => BINARY (via uuid.MarshalBinary())
		Name: "Jean",                 // => VARCHAR
		IP:   net.IPv4(127, 0, 0, 1), // => VARCHAR (via ip.MarshalText())
		Configuration: map[string]interface{}{ // => JSON (via json.Marshal(configuration))
			"darkmode": true,
		},
	}
	_, err = db.InsertRow(ctx, initialUser)
	if err != nil {
		panic(err)
	}

	user := &User{UUID: UUID(uuid)}
	err = db.QueryRow(ctx, &user, nil, nil)
	if err != nil {
		panic(err)
	}

	if reflect.DeepEqual(*user, *initialUser) {
		fmt.Println("They match!")
	}
}
Output:

They match!

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CopySingletonSlice

func CopySingletonSlice(result interface{}, rows []interface{}) error

func CopySlice

func CopySlice(result interface{}, rows []interface{}) error

Types

type BaseSelectQuery

type BaseSelectQuery struct {
	Table   *Table
	Filter  Filter
	Options *SelectOptions
}

func (*BaseSelectQuery) MakeSelectQuery

func (b *BaseSelectQuery) MakeSelectQuery() (*SelectQuery, error)

type BatchInsertQuery

type BatchInsertQuery struct {
	Table   string
	Columns []string
	Values  []interface{}
}

BatchInsertQuery represents a INSERT query with multiple rows

func (*BatchInsertQuery) ToSQL

func (q *BatchInsertQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized INSERT INTO x (a, b) VALUES (?, ?), (?, ?) ... statement

type BatchUpsertQuery

type BatchUpsertQuery struct {
	Table   string
	Columns []string
	Values  []interface{}
}

BatchUpsertQuery represents a INSERT ... ON DUPLICATE KEY UPDATE query with multiple rows

func (*BatchUpsertQuery) ToSQL

func (q *BatchUpsertQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized INSERT INTO x (a, b) VALUES (?, ?) ON DUPLICATE KEY UPDATE query statement

type Column

type Column struct {
	Name    string
	Primary bool

	Descriptor *fields.Descriptor

	Index []int
	Order int
}

type DB

type DB struct {
	Conn   *sql.DB
	Schema *Schema
	// contains filtered or unexported fields
}

DB uses a *sql.DB connection that is established by its owner. DB assumes the database connection exists and is alive at all times during the lifecycle of the object.

func NewDB

func NewDB(conn *sql.DB, schema *Schema) *DB

func (*DB) BaseQuery

func (db *DB) BaseQuery(ctx context.Context, query *BaseSelectQuery) ([]interface{}, error)

func (*DB) Count

func (db *DB) Count(ctx context.Context, model interface{}, filter Filter) (int64, error)

Count counts the number of relevant rows in a database, matching options in filter

model should be a pointer to a struct, for example:

count, err := db.Count(ctx, &User{}, &res, Filter{})
if err != nil { ... }

func (*DB) DeleteRow

func (db *DB) DeleteRow(ctx context.Context, row interface{}) error

DeleteRow deletes a single row from the database, identified by the row's primary key

row should be a pointer to a struct, for example:

user := &User{Id; 10}
if err := db.DeleteRow(ctx, user); err != nil {

func (*DB) FullScanQuery

func (db *DB) FullScanQuery(ctx context.Context, result interface{}, filter Filter, options *SelectOptions) error

FullScanQuery bypasses any index checking on a query. Normal LiveDB.Query will check during tests if the query uses an index and will fail tests if not. This function will skip those checks. There are cases where we explicitly want to support full table scans such as

  1. During tests to verify results (eg get all)
  2. Some rare operations are infrequent and its better to have no index and instead perform full table scans when that query is run.

func (*DB) HasTx

func (db *DB) HasTx(ctx context.Context) bool

HasTx returns whether the provided Context contains a transaction for this DB.

func (*DB) InsertRow

func (db *DB) InsertRow(ctx context.Context, row interface{}) (sql.Result, error)

InsertRow inserts a single row into the database

row should be a pointer to a struct, for example:

user := &User{Name: "foo"}
if err := db.InsertRow(ctx, user); err != nil {

func (*DB) InsertRows

func (db *DB) InsertRows(ctx context.Context, rows interface{}, chunkSize int) error

InsertRows inserts multiple rows into the database, chunksize rows at a time. Most SQL db enforce a limit on max size of packet, which is why we need to break the rows into chunks.

rows should be an array of pointers to a struct, for example:

user1 := &User{Name: "foo"}
user2 := &User{Name: "fan"}
if err := db.InsertRows(ctx, [](*User){user1, user2}, 100); err != nil {

func (*DB) Query

func (db *DB) Query(ctx context.Context, result interface{}, filter Filter, options *SelectOptions) error

Query fetches a collection of rows from the database

result should be a pointer to a slice of pointers to structs, for example:

var users []*User
if err := db.Query(ctx, &users, nil, nil); err != nil {

func (*DB) QueryExecer

func (db *DB) QueryExecer(ctx context.Context) QueryExecer

func (*DB) QueryRow

func (db *DB) QueryRow(ctx context.Context, result interface{}, filter Filter, options *SelectOptions) error

QueryRow fetches a single row from the database

result should be a pointer to a pointer to a struct, for example:

var user *User
if err := db.Query(ctx, &user, Filter{"id": 10}, nil); err != nil {

func (*DB) UpdateRow

func (db *DB) UpdateRow(ctx context.Context, row interface{}) error

UpdateRow updates a single row in the database, identified by the row's primary key

row should be a pointer to a struct, for example:

user := &User{Id; 10, Name: "bar"}
if err := db.UpdateRow(ctx, user); err != nil {

func (*DB) UpsertRow

func (db *DB) UpsertRow(ctx context.Context, row interface{}) (sql.Result, error)

UpsertRow inserts a single row into the database

row should be a pointer to a struct, for example:

user := &User{Name: "foo"}
if err := db.UpsertRow(ctx, user); err != nil {

func (*DB) UpsertRows

func (db *DB) UpsertRows(ctx context.Context, rows interface{}, chunkSize int) error

UpsertRows upserts multiple rows into the database, chunksize rows at a time. Most SQL db enforce a limit on max size of packet, which is why we need to break the rows into chunks.

rows should be an array of pointers to a struct, for example:

user1 := &User{Name: "foo"}
user2 := &User{Name: "fan"}
if err := db.UpsertRows(ctx, [](*User){user1, user2}, 100); err != nil {

func (*DB) WithDynamicLimit

func (db *DB) WithDynamicLimit(dynamicLimit DynamicLimit) (*DB, error)

WithDynamicLimit is similar to WithShardLimit except that it supports dynamically computing what filter to enforce as a limit, based on a user- provide callback. It may be used together with a shard limit.

func (*DB) WithExistingTx

func (db *DB) WithExistingTx(ctx context.Context, tx *sql.Tx) (context.Context, error)

WithExistingTx returns a derived Context that contains the provided Tx. It is an error to invoke this method on a Context that already contains a transaction for this DB. On error WithExistingTx returns a non-nil Context, so that the caller can still easily use its Context (e.g., to log the error).

func (*DB) WithPanicOnNoIndex

func (db *DB) WithPanicOnNoIndex() (*DB, error)

WithPanicOnNoIndex will configure this db connection to run an explain on every query and panic when no index is found. This setting is recommended only for use in testing so that you can find non-indexed queries during tests.

func (*DB) WithShardLimit

func (db *DB) WithShardLimit(shardLimit Filter) (*DB, error)

WithShardLimit scopes the DB to only allow queries with the given key-value pairs. This means any query must include a filter for the key-value pairs in the limit, and any write must have columns including the specified key-value pairs.

func (*DB) WithTx

func (db *DB) WithTx(ctx context.Context) (context.Context, *sql.Tx, error)

WithTx begins a transaction and returns a derived Context that contains that transaction. It also returns the transaction value itself, for the caller to manipulate (e.g., Commit). It is an error to invoke this method on a Context that already contains a transaction for this DB. On error WithTx returns a non-nil Context, so that the caller can still easily use its Context (e.g., to log the error).

type DeleteQuery

type DeleteQuery struct {
	Table string
	Where *SimpleWhere
}

DeleteQuery represents a DELETE query

func (*DeleteQuery) ToSQL

func (q *DeleteQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized DELETE FROM x WHERE a = ? AND b = ? statement

type DynamicLimit

type DynamicLimit struct {
	GetLimitFilter        DynamicLimitFilterCallback
	ShouldContinueOnError DynamicLimitErrorCallback
}

DynamicLimit is used with WithDynamicLimit to register a dynamic limit on a Thunder DB.

A `DynamicLimit` consists of two callbacks, `GetLimitFilter`, and `ShouldContinueOnError`, both user-defined.

Before making an SQL query, a Thunder DB with a dynamic limit will call `GetLimitFilter` with the request context and the name of the table being queried. It will use the sqlgen filter returned by the function as the limit to check for against the current query. The underlying limit check is the same as that used in `WithShardLimit`.

If the limit check fails, Thunder will call `ShouldContinueOnError` with the resulting error object and the table name. If `ShouldContinueOnError` returns false, the query will fail. Otherwise, if the return value is true Thunder will still execute the query.

type DynamicLimitErrorCallback

type DynamicLimitErrorCallback func(err error, table string) bool

type DynamicLimitFilterCallback

type DynamicLimitFilterCallback func(context.Context, string) Filter

type ErrorWithQuery

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

ErrorWithQuery is an error wrapper that includes the clause and arguments of a sqlgen query.

func (*ErrorWithQuery) Error

func (e *ErrorWithQuery) Error() string

func (*ErrorWithQuery) Reason

func (e *ErrorWithQuery) Reason() string

Reason returns a human-readable error message of the clause and args

func (*ErrorWithQuery) Unwrap

func (e *ErrorWithQuery) Unwrap() error

Unwrap returns the error without the clause or arguments.

type ExplainResultRow

type ExplainResultRow struct {
	Id           int64
	SelectType   *string `sql:"select_type"`
	Table        *string
	TypeColumn   *string `sql:"type"`
	PossibleKeys *string `sql:"possible_keys"`
	Key          *string
	KeyLen       *string `sql:"key_len"`
	Ref          *string
	Rows         *int64
	Extra        *string `sql:"Extra"`
}

type Filter

type Filter map[string]interface{}

type InsertQuery

type InsertQuery struct {
	Table   string
	Columns []string
	Values  []interface{}
}

InsertQuery represents a INSERT query

func (*InsertQuery) ToSQL

func (q *InsertQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized INSERT INTO x (a, b) VALUES (?, ?) statement

type NullBytes

type NullBytes struct {
	Bytes []byte
	Valid bool
}

func (*NullBytes) Scan

func (b *NullBytes) Scan(value interface{}) error

func (*NullBytes) Value

func (b *NullBytes) Value() (driver.Value, error)

type PrimaryKeyType

type PrimaryKeyType int
const (
	AutoIncrement PrimaryKeyType = iota
	UniqueId
)

type QueryExecer

type QueryExecer interface {
	Query(query string, args ...interface{}) (*sql.Rows, error)
	QueryRow(query string, args ...interface{}) *sql.Row
	Exec(query string, args ...interface{}) (sql.Result, error)

	QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}

A QueryExecer is either a *sql.Tx or a *sql.DB.

type SQLQuery

type SQLQuery interface {
	ToSQL() (string, []interface{})
}

type Schema

type Schema struct {
	ByName map[string]*Table
	ByType map[reflect.Type]*Table
}

func NewSchema

func NewSchema() *Schema

func (*Schema) BuildStruct

func (s *Schema) BuildStruct(tableName string, row []driver.Value) (interface{}, error)

BuildStruct scans a row in struct's column order into a struct pointer.

func (*Schema) MakeBatchInsertRow

func (s *Schema) MakeBatchInsertRow(rows []interface{}) (*BatchInsertQuery, error)

MakeBatchInsertRow builds a new InsertQuery to insert multiple rows

func (*Schema) MakeBatchUpsertRow

func (s *Schema) MakeBatchUpsertRow(rows []interface{}) (*BatchUpsertQuery, error)

MakeBatchUpsertRow builds a new BatchUpsertQuery to upsert multiple rows

func (*Schema) MakeDeleteRow

func (s *Schema) MakeDeleteRow(row interface{}) (*DeleteQuery, error)

MakeDeleteRow builds a new DeleteQuery to delete row

func (*Schema) MakeInsertRow

func (s *Schema) MakeInsertRow(row interface{}) (*InsertQuery, error)

MakeInsertRow builds a new InsertQuery to insert row

func (*Schema) MakeSelect

func (s *Schema) MakeSelect(result interface{}, filter Filter, options *SelectOptions) (*BaseSelectQuery, error)

func (*Schema) MakeSelectRow

func (s *Schema) MakeSelectRow(result interface{}, filter Filter, options *SelectOptions) (*BaseSelectQuery, error)

func (*Schema) MakeTester

func (s *Schema) MakeTester(table string, filter Filter) (Tester, error)

func (*Schema) MakeUpdateRow

func (s *Schema) MakeUpdateRow(row interface{}) (*UpdateQuery, error)

MakeUpdateRow builds a new UpdateQuery to update row

func (*Schema) MakeUpsertRow

func (s *Schema) MakeUpsertRow(row interface{}) (*UpsertQuery, error)

MakeUpsertRow builds a new UpsertQuery to upsqrt row

func (*Schema) MustRegisterType

func (s *Schema) MustRegisterType(table string, primaryKeyType PrimaryKeyType, value interface{})

func (*Schema) ParseRows

func (s *Schema) ParseRows(query *SelectQuery, res *sql.Rows) ([]interface{}, error)

func (*Schema) RegisterType

func (s *Schema) RegisterType(table string, primaryKeyType PrimaryKeyType, value interface{}) error

func (*Schema) UnbuildStruct

func (s *Schema) UnbuildStruct(tableName string, strct interface{}) ([]interface{}, error)

UnbuildStruct extracts SQL values from a struct.

type SelectOptions

type SelectOptions struct {
	Where  string
	Values []interface{}

	OrderBy   string
	Limit     int
	ForUpdate bool

	AllowNoIndex bool
	// A list of indexes to specify in the USE INDEX hint
	UseIndex []string
	// A list of indexes to specify in the FORCE INDEX hint.
	// If both UseIndex and ForceIndex are provided, only ForceIndex will be used:
	// a query that specifies both USE INDEX and FORCE INDEX would be invalid syntax.
	ForceIndex []string
}

func (*SelectOptions) IncludeFilter

func (s *SelectOptions) IncludeFilter(table *Table, filter Filter) error

type SelectQuery

type SelectQuery struct {
	Table   string
	Columns []string

	Options *SelectOptions
}

SelectQuery represents a SELECT query

func (*SelectQuery) ToSQL

func (q *SelectQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized SELECT a, b, c FROM x ... statement

type SimpleWhere

type SimpleWhere struct {
	Columns []string
	Values  []interface{}
}

SimpleWhere represents a simple WHERE clause

func (*SimpleWhere) ToSQL

func (w *SimpleWhere) ToSQL() (string, []interface{})

ToSQL builds a `a = ? AND b = ?` clause

type Table

type Table struct {
	Name           string
	Type           reflect.Type
	PrimaryKeyType PrimaryKeyType

	Columns       []*Column
	ColumnsByName map[string]*Column

	Scanners *sync.Pool
}

type Tester

type Tester interface {
	Test(row interface{}) bool
}

type UpdateQuery

type UpdateQuery struct {
	Table   string
	Columns []string
	Values  []interface{}
	Where   *SimpleWhere
}

UpdateQuery represents a UPDATE query

func (*UpdateQuery) ToSQL

func (q *UpdateQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized UPDATE x SET a = ?, b = ? WHERE c = ? statement

type UpsertQuery

type UpsertQuery struct {
	Table   string
	Columns []string
	Values  []interface{}
}

UpsertQuery represents a INSERT ... ON DUPLICATE KEY UPDATE query

func (*UpsertQuery) ToSQL

func (q *UpsertQuery) ToSQL() (string, []interface{})

ToSQL builds a parameterized INSERT INTO x (a, b) VALUES (?, ?) statement

Jump to

Keyboard shortcuts

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