crud

package
v0.0.0-...-b066fa0 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2022 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package crud provides Create Read Update and Delete functionality on top of pbpgx. It is capable of effeciently building queries based on incomming protocol buffer messages, and returning results as protocol buffer messages, using protoreflect and generics.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ColNames

type ColNames []string

ColNames expresses proto.Message field names for use as column specifiers.

func ParseFields

func ParseFields(msg proto.Message, skipEmpty bool, ignore ...string) (cols ColNames)

ParseFields returns a slice of field names from the passed proto message. Optionaly, empty (zero-value) fields can be skipped. Note that names are case sensitive, as defined in the proto file, not the Go struct field names.

type Columns

type Columns map[string]OnEmpty

Columns defines the default status of a column value. This affects the write behaviour of emtpy fields during Create (INSERT) and Update (UPDATE) calls.

func (Columns) ParseArgs

func (columns Columns) ParseArgs(msg proto.Message, colNames ColNames) (args []interface{}, err error)

ParseArgs parses the values from the fields named by fieldNames. The returned args contains pgtype values for efficient encoding. Empty fields will be set as `Null` by default, unless when set to `Zero` in Columns. Columns may be nil.

type Enum

type Enum interface {
	query.ColName
	constraints.Integer
}

type OnEmpty

type OnEmpty int
const (
	Null OnEmpty = iota // On empty field, set value to Null in the database.
	Zero                // On empty field, set value to its zero value.
)

type Table

type Table[Col Enum, Record proto.Message, ID constraints.Ordered] struct {
	// contains filtered or unexported fields
}

Table is a re-usable and concurrency safe object for CRUD operations. It holds reference to a table name, optional with schema and optimizes repeated query building for all supported CRUD functions in this package.

func NewTable

func NewTable[Col Enum, Record proto.Message, ID constraints.Ordered](schema, table string, cd Columns) *Table[Col, Record, ID]

NewTable returns a newly allocated table. Schema may be an empty string, in which case it will be ommitted from all queries built for this table. ColumnDefault specifies the behaviour when finding empty fields during data writes of multiple records. (protocol buffers does not have the notion of Null). Single record writes always ommit empty fields and ignore the onEmpty setting.

The Col type parameter is typically an entry of a protocol buffers enum with columns names. (must implement the String() method). The Record type parameter should be a protocol buffer message representing the databae schema. Column names must match with field names, case sensitive. It is recommended to define a field for each column, for usage with the wildcard operator '*'. It is safe to have more fields than columns, the surplus will be ignored. See pbpgx.Scan for details. The ID type parameter should match the type used in "id" column of the table, used to match a single row in Read, Update and Delete.

Example
package main

import (
	"github.com/muhlemmer/pbpgx/crud"
	gen "github.com/muhlemmer/pbpgx/example_gen"
)

func main() {
	columns := crud.Columns{"title": crud.Zero}
	crud.NewTable[gen.ProductColumns_Names, *gen.Product, int32]("public", "example", columns)
}
Output:

func (*Table[Col, Record, ID]) Create

func (tab *Table[Col, Record, ID]) Create(ctx context.Context, x pbpgx.Executor, cols ColNames, data []proto.Message, returnColumns ...Col) (records []Record, err error)

Create one or more records in a Table, with the contents of the req Message. Each field value in data will be set to a corresponding column, matching on the protobuf fieldname, case sensitive. Empty fields are omitted from the query, the resulting values for the corresponding columns will depend on database defaults.

If any returnColumns are specified, the returned records will have the fields set as named by returnColumns. If no returnColumns, the returned slice will always be nil.

Note: Although it is allowed to pass a connection pool as Executor, it is recommended to pass a Conn or Tx type. Create executes the same INSERT query for every entry in Data, And a connection pool does not reuse statements. If the connection is set up in "simple" mode, this function will likely have bad performance. Furthermore, this function makes no assumptions on transactional requirements of the call. Meaning that on error a part of the data may be inserted and will not be rolled back. It is the responsibilty of the caller to Begin and Rollback in case this is required.

func (*Table[Col, Record, ID]) CreateOne

func (tab *Table[Col, Record, ID]) CreateOne(ctx context.Context, x pbpgx.Executor, cols ColNames, data proto.Message, returnColumns ...Col) (record Record, err error)

CreateOne creates one record in a Table, with the contents of data and returns the result in a message of type Record. Each field value in data will be set to a corresponding column from cols, matching on the procobuf fieldname, case sensitive.

If any returnColumns are specified, the returned record will have the fields set as named by returnColumns. If no returnColumns, the returned record will always be nil.

Example
tab := crud.NewTable[gen.ProductColumns_Names, *gen.Product, int32]("public", "products", nil)

ctx, cancel := context.WithTimeout(context.TODO(), 50*time.Second)
defer cancel()

conn, err := pgx.Connect(ctx, "user=pbpgx_tester host=db port=5432 dbname=pbpgx_tester")
if err != nil {
	panic(err)
}
defer conn.Close(ctx)

query := &gen.ProductCreateQuery{
	Data: &gen.Product{
		Title: "Great deal!",
		Price: 9.99,
	},
	Columns: []gen.ProductColumns_Names{
		gen.ProductColumns_id,
		gen.ProductColumns_title,
		gen.ProductColumns_price,
		gen.ProductColumns_created,
	},
}

record, err := tab.CreateOne(ctx, conn, crud.ParseFields(query.GetData(), true), query.GetData(), query.GetColumns()...)
if err != nil {
	panic(err)
}

out, _ := protojson.Marshal(record)
fmt.Println(string(out))

// {"id":"6","title":"Great deal!","price":9.99,"created":"2022-01-07T14:14:38.121500Z"}
Output:

func (*Table[Col, Record, ID]) DeleteOne

func (tab *Table[Col, Record, ID]) DeleteOne(ctx context.Context, x pbpgx.Executor, id ID, returnColumns ...Col) (record Record, err error)

DeleteOne record from a Table, identified by id. Returns the deleted record in a message of type Record.

If any returnColumns are specified, the returned message will have the fields set as named by returnColumns. If no returnColumns, the returned message will always be nil.

func (*Table[Col, Record, ID]) ReadAll

func (tab *Table[Col, Record, ID]) ReadAll(ctx context.Context, x pbpgx.Executor, limit int64, columns []Col, orderBy query.OrderWriter[Col]) ([]Record, error)

ReadAll records up to limit from a table. The returned messages will be a slice of type Record, with the fields corresponding to columns populated.

func (*Table[Col, Record, ID]) ReadList

func (tab *Table[Col, Record, ID]) ReadList(ctx context.Context, x pbpgx.Executor, ids []ID, columns []Col, orderBy query.OrderWriter[Col]) ([]Record, error)

ReadList returns a list of records from a table, identified by ids. The returned messages will be a slice of type Record, with the fields corresponding to columns populated.

func (*Table[Col, Record, ID]) ReadOne

func (tab *Table[Col, Record, ID]) ReadOne(ctx context.Context, x pbpgx.Executor, id ID, columns []Col) (record Record, err error)

ReadOne record from a table, identified by id. The returned message will be of type Record, with the fields corresponding to columns populated.

Example
tab := crud.NewTable[gen.ProductColumns_Names, *gen.Product, int64]("public", "products", nil)

ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()

conn, err := pgx.Connect(ctx, "user=pbpgx_tester host=db port=5432 dbname=pbpgx_tester")
if err != nil {
	panic(err)
}
defer conn.Close(ctx)

query := &gen.ProductQuery{
	Id: 2,
	Columns: []gen.ProductColumns_Names{
		gen.ProductColumns_title,
		gen.ProductColumns_price,
		gen.ProductColumns_created,
	},
}

record, err := tab.ReadOne(ctx, conn, query.Id, query.Columns)
if err != nil {
	panic(err)
}

out, _ := protojson.Marshal(record)
fmt.Println(string(out))

// {"title":"two","price":10.45,"created":"2022-01-07T13:47:08Z"}
Output:

func (*Table[Col, Record, ID]) UpdateOne

func (tab *Table[Col, Record, ID]) UpdateOne(ctx context.Context, x pbpgx.Executor, cols ColNames, id ID, data proto.Message, returnColumns ...Col) (record Record, err error)

UpdateOne updates one record in a Table, identified by id, with the contents of the data Message and returns the result in a message of type Record. Each field value in data will be set to a corresponding column, matching on the protobuf fieldname, case sensitive.

If any returnColumns are specified, the returned message will have the fields set as named by returnColumns. If no returnColumns, the returned message will always be nil.

Jump to

Keyboard shortcuts

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