pika

package module
v0.0.0-...-b5cc282 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2024 License: Apache-2.0 Imports: 23 Imported by: 0

README

pika

ORM-like SQL builder inspired by kayak/pypika

Features

  • Any feature supported by sqlx
  • Support for AIP-160 filtering
  • Utilities to help with AIP-132 for List calls
  • Support for determining filters based on Protobuf messages
  • Automatically selecting columns in struct
  • Count, Get, All, Create, Update, Delete and more.
  • Support for simple joins

Example

Simple connect and Get
package main

import (
	"go.ciq.dev/pika"
	"log"
)

type User struct {
	PikaTableName string `pika:"users"`
	ID            int64  `db:"id" pika:"omitempty"`
	Name          string `db:"name"`
}

func main() {
	psql, _ := pika.NewPostgreSQL("postgres://postgres:postgres@localhost:5432/test")
	args := pika.NewArgs()
	args.Set("id", 1)
	qs := pika.Q[User](psql).Filter("id=:id").Args(args)
	user, _ := qs.Get()
	
	log.Println(user)
}
AIP-160
package main

import (
	"go.ciq.dev/pika"
	"log"
)

type Article struct {
	PikaTableName string    `pika:"users"`
	ID            int64     `db:"id" pika:"omitempty"`
	CreatedAt     time.Time `db:"created_at" pika:"omitempty"`
	Title         string    `db:"title"`
	Body          string    `db:"body"`
}

func main() {
	psql, _ := pika.NewPostgreSQL("postgres://postgres:postgres@localhost:5432/test")

	qs := pika.Q[Article](s.db)
	// Get the following articles
	//   * The title MUST contain Hello and the body MUST contain World
	//   * If the above is not match, the article MUST be created before 2023-07-30
	qs, err := qs.AIP160(`(title:"Hello" AND body:"World") OR (created_at < 2023-07-30T00:00:00Z)`, pika.AIPFilterOptions{})
	if err != nil {
		return nil, err
	}

	rows, err := qs.All()
	if err != nil {
		return nil, err
	}

	log.Println(rows)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	PikaMetadataTableName      = "PikaTableName"
	PikaMetadataDefaultOrderBy = "PikaDefaultOrderBy"
	PikaMetadataFields         = []string{
		PikaMetadataTableName,
		PikaMetadataDefaultOrderBy,
	}
)
View Source
var (

	// Operators
	// Equal
	OpEq = "="
	// Not equal
	OpNeq = "!="
	// Greater than
	OpGt = ">"
	// Greater than or equal
	OpGte = ">="
	// Less than
	OpLt = "<"
	// Less than or equal
	OpLte = "<="
	// Like
	OpLike = "LIKE"
	// Not like
	OpNotLike = "NOT LIKE"
	// ILike
	OpILike = "ILIKE"
	// Not ILike
	OpNotILike = "NOT ILIKE"
	// Is null
	OpIsNull = "IS NULL"
	// Is not null
	OpIsNotNull = "IS NOT NULL"
	// Empty
	OpEmpty = ""

	// Hints
	// Negate
	HintNegate = "__ne"
	// In
	HintIn = "__in"
	// Not in
	HintNotIn = "__nin"
	// Greater than
	HintGt = "__gt"
	// Greater than or equal
	HintGte = "__gte"
	// Less than
	HintLt = "__lt"
	// Less than or equal
	HintLte = "__lte"
	// Like
	HintLike = "__like"
	// Not like
	HintNotLike = "__nlike"
	// ILike
	HintILike = "__ilike"
	// Not ILike
	HintNotILike = "__nilike"
	// Is null
	HintIsNull = "__null"
	// Is not null
	HintIsNotNull = "__notnull"
	// Or
	HintOr = "__or"
	// And
	HintAnd = "__and"
	// Empty
	HintEmpty = ""
)

Functions

func NewArgs

func NewArgs() *orderedmap.OrderedMap[string, any]

Types

type AIPFilter

type AIPFilter[T any] struct {
	QuerySet[T]
}

func NewAIPFilter

func NewAIPFilter[T any]() *AIPFilter[T]

type AIPFilterIdentifier

type AIPFilterIdentifier struct {
	// Value aliases are used to map a value to a different value.
	// Mostly useful for enums, where for example the values
	// STAGE_STATUS_FAILED, FAILED, FaIlEd, etc. should all be
	// mapped to the same value.
	// This means that the alias value is case insensitive.
	// Make sure to convert to lower case if key is a string.
	ValueAliases map[any]any

	// AcceptedTypes is a list of types that are accepted for this
	// identifier.
	// If empty, all types are accepted.
	// The value should be in antlrValues
	AcceptedTypes []int

	// AcceptableValues is a list of values that are accepted for this
	// identifier.
	// If empty, all values are accepted.
	AcceptedValues []any

	// Column name is the name of the column in the database.
	// If empty, the identifier is used as the column name.
	ColumnName string

	// IsRepeated is true if the identifier is a repeated field.
	// This is used to determine how to apply the filter.
	IsRepeated bool
}

type AIPFilterOptions

type AIPFilterOptions struct {
	// Identifiers are additional configuration for specific identifiers.
	Identifiers map[string]AIPFilterIdentifier

	// AcceptableIdentifiers is a list of identifiers that are allowed
	AcceptableIdentifiers []string
}

func ProtoReflect

func ProtoReflect(m proto.Message) AIPFilterOptions

func ProtoReflectWithOpts

func ProtoReflectWithOpts(m proto.Message, opts ProtoReflectOptions) AIPFilterOptions

type CreateOption

type CreateOption byte
const InsertOnConflictionDoNothing CreateOption = 1 << iota

type PageRequest

type PageRequest struct {
	// Filter is a filter expression that restricts the results to return.
	Filter string

	// OrderBy is a comma-separated list of fields to order by.
	OrderBy string

	// PageSize is the maximum number of results to return.
	PageSize int32

	// PageToken is the page token to use for the next request.
	PageToken string
}

func (*PageRequest) GetFilter

func (p *PageRequest) GetFilter() string

func (*PageRequest) GetOrderBy

func (p *PageRequest) GetOrderBy() string

func (*PageRequest) GetPageSize

func (p *PageRequest) GetPageSize() int32

func (*PageRequest) GetPageToken

func (p *PageRequest) GetPageToken() string

type PageToken

type PageToken[T any] struct {
	QuerySet[T] `json:"-"`

	Offset   uint   `json:"offset"`
	Filter   string `json:"filter"`
	OrderBy  string `json:"order_by"`
	PageSize uint   `json:"page_size"`
}

func NewPageToken

func NewPageToken[T any]() *PageToken[T]

func (*PageToken[T]) Decode

func (p *PageToken[T]) Decode(s string) error

Decode constructs a PageToken from a base64-encoded string

func (*PageToken[T]) Encode

func (p *PageToken[T]) Encode() (string, error)

Encode marshals the PageToken to a base64-encoded string

type Paginatable

type Paginatable interface {
	GetFilter() string
	GetOrderBy() string
	GetPageSize() int32
	GetPageToken() string
}

type PostgreSQL

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

func NewPostgreSQL

func NewPostgreSQL(connectionString string) (*PostgreSQL, error)

NewPostgreSQL returns a new PostgreSQL instance. connectionString should be sqlx compatible.

func NewPostgreSQLFromDB

func NewPostgreSQLFromDB(db *sqlx.DB) *PostgreSQL

func (*PostgreSQL) Begin

func (p *PostgreSQL) Begin(ctx context.Context) error

Begin starts a new transaction.

func (*PostgreSQL) Close

func (p *PostgreSQL) Close() error

func (*PostgreSQL) Commit

func (p *PostgreSQL) Commit() error

Commit commits the current transaction.

func (*PostgreSQL) DB

func (p *PostgreSQL) DB() *sqlx.DB

func (*PostgreSQL) Queryable

func (p *PostgreSQL) Queryable() Queryable

func (*PostgreSQL) Rollback

func (p *PostgreSQL) Rollback() error

Rollback rolls back the current transaction.

func (PostgreSQL) TableAlias

func (c PostgreSQL) TableAlias(src string, dst string)

type ProtoReflectOptions

type ProtoReflectOptions struct {
	// Exclude is a list of field names to exclude from the filter
	// Uses proto name always, not JSON name
	Exclude []string

	// ColumnName is a function that returns the column name for a given field
	// name. If not provided, the field name is used.
	ColumnName func(string) string
}

type QuerySet

type QuerySet[T any] interface {
	// Filter returns a new QuerySet with the given filters applied.
	// The filters are applied in the order they are given.
	// Only use named parameters in the filters.
	// Multiple filter calls can be made, they will be combined with AND.
	// Will also work as AND combined
	// See FilterOr for OR combined.
	// See FilterInnerOr for inner filters combined with OR.
	// See FilterOrInnerOr for inner filters combined with OR.
	// Filter keys can also contain various hints (use as suffix to filter key):
	//  - "__ne" to negate the filter
	//  - "__in" to use an IN clause
	//  - "__nin" to use a NOT IN clause
	//  - "__gt" to use a > clause
	//  - "__gte" to use a >= clause
	//  - "__lt" to use a < clause
	//  - "__lte" to use a <= clause
	//  - "__like" to use a LIKE clause
	//  - "__nlike" to use a NOT LIKE clause
	//  - "__ilike" to use a ILIKE clause
	//  - "__nilike" to use a NOT ILIKE clause
	//  - "__null" to use a IS NULL clause
	//  - "__notnull" to use a IS NOT NULL clause
	//  - "__or" to prepend with OR instead of AND (in AND filter calls)
	//  - "__and" to prepend with AND instead of OR (in OR filter calls)
	Filter(queries ...string) QuerySet[T]

	// FilterOr returns a new QuerySet with the given filters applied.
	// The filters are applied in the order they are given.
	// Only use named parameters in the filters.
	// Multiple filter calls can be made, they will be combined with AND.
	// But will work as OR combined
	// See Filter for AND combined.
	FilterOr(queries ...string) QuerySet[T]

	// FilterInnerOr returns a new QuerySet with the given filters applied.
	// Same as Filter, but inner filters are combined with OR.
	FilterInnerOr(queries ...string) QuerySet[T]

	// FilterOrInnerOr returns a new QuerySet with the given filters applied.
	// Same as FilterOr, but inner filters are combined with OR.
	FilterOrInnerOr(queries ...string) QuerySet[T]

	// Args sets named arguments for the filters.
	// The arguments are applied in the order they are given.
	Args(args *orderedmap.OrderedMap[string, interface{}]) QuerySet[T]

	// ClearArgs clear filters, args, and any previous set joins
	ClearAll() QuerySet[T]

	// Create creates a new value
	Create(ctx context.Context, value *T, options ...CreateOption) error

	// Update updates a value
	// All filters will be applied
	Update(ctx context.Context, value *T) error

	// Delete deletes a row
	// All filters will be applied
	Delete(ctx context.Context) error

	// GetOrNil returns a single value or nil
	// Multiple values will return an error.
	// Ignores Limit
	GetOrNil(ctx context.Context) (*T, error)

	// Get returns a single value
	// Returns error if no value is found
	// Returns error if multiple values are found
	// Ignores Limit
	Get(ctx context.Context) (*T, error)

	// All returns all values
	All(ctx context.Context) ([]*T, error)

	// Count returns the number of values
	Count(ctx context.Context) (int, error)

	// Limit sets the limit for the query
	Limit(limit int) QuerySet[T]

	// Offset sets the offset for the query
	Offset(offset int) QuerySet[T]

	// OrderBy sets the order for the query
	// Use - to indicate descending order
	// Example:
	// 	OrderBy("-id", "name")
	OrderBy(order ...string) QuerySet[T]

	// ResetOrderBy resets the order for the query
	ResetOrderBy() QuerySet[T]

	// CreateQuery returns the query and args for Create
	CreateQuery(value *T, options ...CreateOption) (string, []interface{})

	// UpdateQuery returns the query and args for Update
	UpdateQuery(value *T) (string, []interface{})

	// DeleteQuery returns the query and args for Delete
	DeleteQuery() (string, []interface{})

	// GetOrNilQuery returns the query and args for GetOrNil
	GetOrNilQuery() (string, []interface{})

	// GetQuery returns the query and args for Get
	GetQuery() (string, []interface{})

	// AllQuery returns the query and args for All
	AllQuery() (string, []interface{})

	// Extensions
	// AIP-160 filtering for gRPC/Proto
	// See https://google.aip.dev/160
	AIP160(filter string, options AIPFilterOptions) (QuerySet[T], error)

	// Page token functionality for gRPC
	// The count is optional and returns the total number of rows for the query.
	// It is implemented as a variadic function to not break existing code.
	GetPage(ctx context.Context, paginatable Paginatable, options AIPFilterOptions, count ...*int) ([]*T, string, error)

	// Join table
	InnerJoin(modelFirst, modelSecond interface{}, keyFirst, keySecond string) QuerySet[T]
	LeftJoin(modelFirst, modelSecond interface{}, keyFirst, keySecond string) QuerySet[T]
	RightJoin(modelFirst, modelSecond interface{}, keyFirst, keySecond string) QuerySet[T]
	FullJoin(modelFirst, modelSecond interface{}, keyFirst, keySecond string) QuerySet[T]

	// Exclude fields
	Exclude(excludes ...string) QuerySet[T]
	// Include fields
	Include(includes ...string) QuerySet[T]

	// U is a shorthand for Update. ID field is used as the filter.
	// Other filters applied to the query set are also inherited.
	// Returns an error if the ID field is not set or does not exist.
	// Thus preventing accidental updates to all rows.
	U(ctx context.Context, value *T) error

	// F is a shorthand for Filter. It is a variadic function that accepts a list of filters.
	// The filters are applied in the order they are given.
	// Format is as follows: <KEY>, <VALUE> etc.
	F(keyval ...any) QuerySet[T]

	// D is a shorthand for Delete. ID field is used as the filter.
	// Other filters applied to the query set are also inherited.
	// Returns an error if the ID field is not set or does not exist.
	// Thus preventing accidental deletes to all rows.
	D(ctx context.Context, value *T) error

	// Transaction is a shorthand for wrapping a query set in a transaction.
	// Currently Pika transactions affects the full connection, not just the query set.
	// That method works if you use factories to create query sets.
	// This helper will re-use the internal DB instance to return a new query set with the transaction.
	Transaction(ctx context.Context) (QuerySet[T], error)
}

QuerySet is a chainable interface for building queries. WARNING: This interface is not thread-safe and is meant to be used in a single goroutine. Do not re-use a QuerySet after calling All, Get, GetOrNil, Count, or any other method that returns a value. Creating a QuerySet is cheap and is meant to be discarded after use. It builds up a query string and arguments, and then executes the query. The query set can be passed onto another goroutine, given that it is modified only by one party. For safety, DO NOT pass a QuerySet to another goroutine and always create a new one. Use Q to create a new QuerySet.

func PSQLQuery

func PSQLQuery[T any](p *PostgreSQL) QuerySet[T]

func Q

func Q[T any](x any) QuerySet[T]

type Queryable

type Queryable interface {
	sqlx.Ext
	sqlx.ExecerContext
	sqlx.PreparerContext
	sqlx.QueryerContext
	sqlx.Preparer

	GetContext(context.Context, interface{}, string, ...interface{}) error
	MustExecContext(context.Context, string, ...interface{}) sql.Result
	NamedExecContext(context.Context, string, interface{}) (sql.Result, error)
	PrepareNamedContext(context.Context, string) (*sqlx.NamedStmt, error)
	PreparexContext(context.Context, string) (*sqlx.Stmt, error)
	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
	SelectContext(context.Context, interface{}, string, ...interface{}) error
}

Queryable includes all methods shared by sqlx.DB and sqlx.Tx, allowing either type to be used interchangeably.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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