rem

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2023 License: ISC Imports: 9 Imported by: 0

README

REM

The retro Golang ORM. Retro Entity Mapper.

type Accounts struct {
	Group rem.NullForeignKey[Groups] `db:"group_id"`
	Id    int64                      `db:"id" db_primary:"true"`
	Name  string                     `db:"name"`
}

type Groups struct {
	Accounts rem.OneToMany[Accounts] `db:"group_id"`
	Id       int64                   `db:"id" db_primary:"true"`
	Name     string                  `db:"name" db_max_length:"100"`
}
// Only one additional query is executed to fetch all related accounts.
groups, err := rem.Use[Groups]().
	FetchRelated("Accounts").
	Filter("id", "IN", []interface{}{10, 20, 30}).
	Sort("name", "-id").
	All(db)

if err != nil {
	panic(err)
}
for _, group := range groups {
	// group *Groups
	// group.Accounts.Rows []*Accounts
}

Features

  • PostgreSQL and MySQL dialects. SQLite coming soon.
  • Data and schema migrations that use the same model syntax.
  • Optimized foreign key and one-to-many prefetching.
  • Interface extensible query builder. Can be used for database-specific features.
  • Negligible performance difference from using database/sql directly.
  • Decoupled from database/sql connections and drivers.
  • Partially or fully fallback to a safely parameterized SQL format as desired.
  • Zero code gen. Models are just structs that may have your own fields and methods.
  • Standardized safety with explicitly null and not-null types.
  • Transaction and golang context support.
  • Subqueries, joins, selective fetching, map scanning, and more.

Installation

The main branch contains the latest release. From the shell:

go get github.com/evantbyrne/rem

Note: REM is not yet stable and pre-1.0 releases may result in breaking changes.

Contributing

Please post feature requests, questions, and other feedback to the discussions board. Submit bug reports to the issue tracker.

Dialects

REM supports PostgreSQL and MySQL. SQLite support is planned. To use a dialect, import the appropriate package and set it as the default once on application bootup.

import (
	// Choose one:
	"github.com/evantbyrne/rem/mysqldialect"
	"github.com/evantbyrne/rem/pqdialect"
	"github.com/evantbyrne/rem/sqlitedialect"

	// Don't forget to import your database driver.
)
// Choose one:
rem.SetDialect(mysqldialect.MysqlDialect{})
rem.SetDialect(pqdialect.PqDialect{})
rem.SetDialect(sqlitedialect.SqliteDialect{})

// Then connect to your database as usual.
db, err := sql.Open("<driver>", "<connection string>")
if err != nil {
	panic(err)
}
defer db.Close()

Models

Models are structs that define table schemas.

type Accounts struct {
	Id   int64  `db:"id" db_primary:"true"`
	Name string `db:"name" db_max_length:"100"`
	Junk string
}

In the above struct definition, Id and Name are columns in the accounts. Their columns are defined by the db field tag. Id is also an auto-incrementing primary key. Name has a maximum character length of 100. The Junk field is ignored by REM.

After defining a model, register it once on application bootup, then query the database.

// rem.Register[To]() caches computed structure of the model.
rem.Register[Accounts]()

// rem.Use[To]() returns a query builder for the model.
rows, err := rem.Use[Accounts]().All(db)

// You can also reuse the Model[To] instance returned by rem.Register[To]() and rem.Use[To]().
accounts := rem.Use[Accounts]()
rows1, err1 := accounts.Filter("name", "=", "foo").All(db)
rows2, err2 := accounts.Filter("name", "=", "bar").All(db)

// Register and use a different table with the same model.
rem.Register[Accounts](rem.Config{Table: "groups"})
groups := rem.Use[Accounts](rem.Config{Table: "groups"})

Migrations

REM provides the migrations interface as a way to simplify schema and data changes. The interface is just two methods to implement:

// github.com/evantbyrne/rem/migrations.go:
type Migration interface {
	Down(db *sql.DB) error
	Up(db *sql.DB) error
}

Models are defined in the same way within migrations as they are in the rest of the application. Here's an example:

type Migration0001Accounts struct{}

func (m Migration0001Accounts) Up(db *sql.DB) error {
	// We embed the Accounts model to avoid colliding with the package-level Accounts model used for queries. You could also use `rem.Config` as demonstrated in the Models documentation section.
	type Accounts struct {
		Id   int64  `db:"id" db_primary:"true"`
		Name string `db:"name" db_max_length:"100"`
	}

	// Note that we don't use rem.Register[To](), because we don't want to cache the model structure used within the migration.
	_, err := rem.Use[Accounts]().TableCreate(db)
	return err
}

func (m Migration0001Accounts) Down(db *sql.DB) error {
	// Fields aren't needed for dropping a table.
	type Accounts struct{}

	_, err := rem.Use[Accounts]().TableDrop(db)
	return err
}

Then run the migrations:

logs, err := rem.MigrateUp(db, []rem.Migration{
	Migration0001Accounts{},
	// More migrations...
})
// logs []string
// For example: {"Migrating up to Migration0001Accounts..."}

REM will create a migrationlogs table to track which migrations have been run. Execution of subsequent migrations will stop if an error is returned. Use rem.MigrateDown(*sql.DB, []rem.Migration) to run migrations in reverse.

Fields

Field Types

REM determines column types based on Go field types. The following table shows the default column types for each Go primative.

Note: REM uses special Go types for nullable columns. Don't use pointers for model fields.

Go MySQL PostgreSQL SQLite
bool BOOLEAN BOOLEAN BOOLEAN[1]
[]byte - - -
int8 TINYINT SMALLINT INTEGER
int16 SMALLINT SMALLINT INTEGER
int32 INTEGER INTEGER INTEGER
int64 BIGINT BIGINT INTEGER
float32 FLOAT - REAL
float64 DOUBLE DOUBLE PRECISION REAL
string VARCHAR,TEXT[2] VARCHAR,TEXT[2] TEXT
time.Time DATETIME[3] TIMESTAMP[4] DATETIME

[1] SQLite BOOLEAN behaves as an INTEGER internally. The SQLite driver should automatically convert bool field values to 0 or 1 when parameterized.

[2] The VARCHAR column type is used for string and sql.NullString fields when the db_max_length field tag is provided. Otherwise, TEXT is used.

[3] Go's most popular MySQL driver requires adding the parseTime=true GET parameter to the connection string to properly scan into time.Time and sql.NullTime fields.

[4] The PostgreSQL dialect defaults to WITHOUT TIME ZONE for time types. Add the db_time_zone:"true" field tag to use WITH TIME ZONE instead.

Columns are not nullable by default. REM uses the standard database/sql package types to represent nullable columns.

Not Null Nullable
bool sql.NullBool
float64 sql.NullFloat64
int16 sql.NullInt16
int32 sql.NullInt32
int64 sql.NullInt64
rem.ForeignKey[To] rem.NullForeignKey[To]
string sql.NullString
time.Time sql.NullTime

Primary keys are specified with the db_primary:"true" field tag. All models must have a primary key. Integer fields that are primary keys will auto-increment.

// An auto-incrementing primary key.
type A struct {
	Id int64  `db:"id" db_primary:"true"`
}

// VARCHAR primary key with no default value.
type B struct {
	Guid string `db:"guid" db_max_length:"36" db_primary:"true"`
}
Default

The db_default field tag applies a default value to columns. It accepts any string.

Note: Values provided to db_default are not escaped or otherwise sanitized.

// This timestamp uses the SQL function now() for its default value.
type Logs struct {
	CreatedAt time.Time `db:"created_at" db_default:"now()"`
	// ...
}
Unique

The db_unique:"true" field tag applies a unique constraint to a column.

type Accounts struct {
	Nickname string `db:"created_at" db_unique:"true"`
	// ...
}
Custom Types

Custom column types can be set using the db_type field tag, which accpets any string value.

Note: Values provided to db_type are not escaped or otherwise sanitized.

// An example of using PostgreSQL's JSONB type.
type A struct {
	Id   int64  `db:"id" db_primary:"true"`
	Data []byte `db:"data" db_type:"JSONB NOT NULL"`
}

// db_type takes priority over all other field tags, including primary key typing.
type B struct {
	Guid string `db:"guid" db_type:"CHAR(36) NOT NULL" db_primary:"true"`
}

Custom Go types may also be used for model fields, but they must implement the driver.Valuer and sql.Scanner interfaces in additon to being supported by your database driver.

Foreign Keys

Foreign keys are specified with the rem.ForeignKey[To] and rem.NullForeignKey[To] field types. REM automatically matches the foreign key column type to the primary key of the target model.

On the other side of the relation, use rem.OneToMany[To]. On both sides of the relation, the db field tag signifies the column on the rem.ForeignKey[To] side.

type Groups struct {
	Members rem.OneToMany[Members] `db:"group_id"`
	Id      int64                  `db:"id" db_primary:"true"`
}

type Members struct {
	Group rem.ForeignKey[Groups] `db:"group_id"`
	Id    int64                  `db:"id" db_primary:"true"`
}

See Fetch Related for information on querying relationships effeciently.

Relations may also be queried lazily.

// Lazily fetch from a one-to-many field.
group, err := rem.Use[Groups]().Filter("id", "=", 100).First(db)
if err != nil {
	panic(err)
}
accounts, err := group.Members.All(db)
// accounts []*Accounts

// Lazily fetch from a foreign key field.
account, err := rem.Use[Accounts]().Filter("id", "=", 200).First(db)
if err != nil {
	panic(err)
}
group, err := account.Group.Fetch(db)
// group *Groups

Foreign key ON DELETE and ON UPDATE constraints, such as CASCADE or SET NULL, may be set with the db_on_delete and db_on_update field tags.

type Members struct {
	Group rem.NullForeignKey[Groups] `db:"group_id" db_on_delete:"SET NULL" db_on_update:"SET NULL"`
	// ...
}

Reference

All

Executes a query and returns a list of records.

accounts, err := rem.Use[Accounts]().All(db)
// accounts []*Accounts

accounts, err := rem.Use[Accounts]().AllToMap(db)
// accounts []map[string]interface{}
Context

Pass a Golang context to queries.

var ctx context.Context
rem.Use[Accounts]().Context(ctx).All(db)
Count

The Count convenience method returns the number of matching records.

count, err := rem.Use[Accounts]().Filter("id", "<", 100).Count(db)
// count uint
Delete

The Delete convenience method deletes matching records.

results, err := rem.Use[Accounts]().Filter("id", "=", 100).Delete(db)
// results sql.Result
Dialect

Set the dialect for a specific query. This takes priority over the default dialect.

rem.Use[Accounts]().Dialect(mysqldialect.Dialect{}).All(db)

REM can optimize foreign key and one-to-many record lookups. This is done with the FetchRelated method, which takes any number of strings that represent the relation fields to prefetch.

Regardless of which side of the relationship you start from or how many records are being fetched initially, REM will only execute one additional query for prefetching.

// Model definitions for Groups <->> Accounts relationship.
type Accounts struct {
	Group rem.ForeignKey[Groups] `db:"group_id"`
	Id    int64                  `db:"id" db_primary:"true"`
	Name  string                 `db:"name" db_max_length:"100"`
}

type Groups struct {
	Accounts rem.OneToMany[Accounts] `db:"group_id"`
	Id       int64                   `db:"id" db_primary:"true"`
	Name     string                  `db:"name" db_max_length:"100"`
}
groups, err := rem.Use[Groups]().FetchRelated("Accounts").All(db)
for _, group := range groups {
	// group *Groups
	// group.Accounts.Rows []*Accounts
}

accounts, err := rem.Use[Accounts]().FetchRelated("Group").All(db)
for _, account := range accounts {
	// account *Accounts
	// account.Group.Row *Groups
	// account.Group.Valid bool
}
Filter

REM provides a few mechanisms for filtering database results. The most basic is the Filter method, which takes a left side value, operator, and right side value.

Typically, the left side is a column name, which is represented by a string.

The operator is always a string. Use uppercase for alphabetical operators such as "IN", "NOT IN", "IS", "IS NOT", "EXISTS", and so on.

The right side may be any value supported by the database driver for parameterization.

The left and right sides may also be rem.DialectStringerWithArgs, rem.DialectStringer, or rem.SqlUnsafe. These types are used for more advanced filtering, such as subqueries, joins, or SQL function calls.

rem.Use[Accounts]().Filter("id", ">=", 100).All(db)

// Filters may be chained. This is equivalent to "SELECT * FROM accounts WHERE id >= 100 AND id < 200".
rem.Use[Accounts]().
	Filter("id", ">=", 100).
	Filter("id", "<", 200).
	All(db)

// Chain filters with an OR using `rem.Q`. This is equivalent to "SELECT * FROM accounts WHERE name = 'foo' OR (id >= 100 AND id < 200").
rem.Use[Accounts]().
	FilterOr(
		rem.Q("name", "=", "foo"),
		rem.And(
			rem.Q("id", ">=", 100),
			rem.Q("id", "<", 200),
		),
	).
	All(db)

// Complex chained and nested filters are fully supported.
rem.Use[Accounts]().
	FilterAnd(
		rem.Q("a", "=", "foo"),
		rem.Or(
			rem.Q("ab", "=", "bar"),
			rem.And(
				rem.Q("abc1", ">", 100),
				rem.Q("abc2", "<", 200),
			),
		),
	).
	FilterOr(
		rem.Q("b1", "IS", nil),
		rem.Q("b2", "IN", interface{}{10, 20, 30}),
	).
	All(db)
Custom SQL

Safely parameterized SQL may be embedded via the rem.Sql() and rem.Param() functions. String arguments to rem.Sql() are not escaped or otherwise sanitized. rem.Param() arguments are parameterized by the database driver.

// SQL: SELECT * FROM logs WHERE data.tags ?| array[$1]
// Parameters: []interface{}{"foo"}
rem.Use[Logs]().
	Filter("data.tags", "?|", rem.Sql("array[", rem.Param("foo"), "]")).
	All(db)

Raw SQL may also be embedded into either the left or right side of a filters via the rem.Unsafe() function.

Note: Values provided to rem.Unsafe() are not escaped or otherwise sanitized. Only use this function with trusted values.

// SQL: SELECT * FROM accounts WHERE upper(name) = $1
// Parameters: []interface{}{"FOO"}
rem.Use[Accounts]().
	Filter(rem.Unsafe("upper(name)"), "=", "FOO").
	All(db)
Subqueries

REM allows subqueries to be embedded via the standard query syntax.

// SQL: SELECT * FROM accounts WHERE id IN (SELECT account_id FROM groups WHERE name = $1)
// Parameters: []interface{}{"Group 1"}
rem.Use[Accounts]().
	Filter("id", "IN", rem.Use[Groups]().Select("account_id").Filter("name", "=", "Group 1")).
	All(db)

The rem.Exists() and rem.NotExists() functions are provided as a convenience for subqueries that only need to check for the existence of a record.

rem.Column() is also used in the following example to properly handle the column name that is used on the right side of a filter.

// SQL: SELECT * FROM groups WHERE EXISTS (SELECT * FROM accounts WHERE accounts.group_id = groups.id)
rem.Use[Groups]().
	FilterAnd(
		rem.Exists(rem.Use[Accounts]().Filter("accounts.group_id", "=", rem.Column("groups.id")))
	).
	All(db)
First

The First convenience method returns a single record. A sql.ErrNoRows error is returned if no matching records are found.

account, err := rem.Use[Accounts]().Filter("id", "=", 1).First(db)
// account *Accounts

account, err := rem.Use[Accounts]().Filter("id", "=", 1).FirstToMap(db)
// account map[string]interface{}
Insert

The Insert method adds new records to the database.

The first argument is a *sql.DB instance.

The second argument is a pointer to the new record.

Note: Zero-valued primary keys aren't included in inserts via the Insert method.

account := &Accounts{
	Name: "New Name",
}

results, err := rem.Use[Accounts]().Insert(db, account)
// results sql.Result

REM also provides a UpdateMap convenience method that updates matching records with all columns provided by a map[string]interface{}.

Note: Zero-valued primary keys will be included when provided to inserts via the InsertMap method.

account := map[string]interface{}{
	"name": "New Name",
}

results, err := rem.Use[Accounts]().InsertMap(db, account)
Join

The Join, JoinFull, JoinLeft, and JoinRight methods are for performing their respective types of SQL joins.

The first argument is the table to join.

The second argument takes any number of filters to join on.

rows, err := rem.Use[Accounts]().
	Select("accounts.id", "accounts.name", rem.As("groups.name", "group_name")).
	Join("groups", rem.Q("groups.id", "=", rem.Column("accounts.group_id"))).
	AllToMap(db)

// Use a custom model.
type AccountsWithGroupName struct {
	GroupName string `db:"group_name"`
	Id        string `db:"id" db_primary:"true"`
	Name      string `db:"name"`
}

rows, err := rem.Use[AccountsWithGroupName](rem.Config{Table: "accounts"}).
	Select(rem.As("accounts.id", "id"), rem.As("accounts.name", "name"), rem.As("groups.name", "group_name")).
	Join("groups", rem.Q("groups.id", "=", rem.Column("accounts.group_id"))).
	All(db)

// Use Query() to join without selecting columns.
rows, err := rem.Use[Accounts]().
	Query().
	JoinFull("groups", rem.Or(
		rem.Q("groups.id", "IS", nil),
		rem.Q("groups.id", "=", rem.Column("accounts.group_id")),
	).
	AllToMap(db)
Limit and Offset

The Limit and Offset methods both take a single int64 argument.

// LIMIT 10
rem.Use[Accounts]().Limit(10).All(db)

// LIMIT 10 OFFSET 20
rem.Use[Accounts]().Limit(10).Offset(20).All(db)
Scan Map

The ScanMap convenience method converts a map[string]interface{} into a model pointer.

data := map[string]interface{}{
	"id":   100,
	"name": "New Name",
}

account, err := rem.Use[Accounts].ScanMap(data)
// account *Accounts
Select

By default, queries scans all columns on the model. The Select method takes any number of strings, which when present, represent the only columns to scan. It also accepts rem.DialectStringer, and rem.SqlUnsafe values for special cases.

// SELECT id FROM accounts
rem.Use[Accounts]().Select("id").All(db)

// SELECT id, UPPER(name) as name FROM accounts
rem.Use[Accounts]().Select("id", rem.Unsafe("UPPER(name) as name")).All(db)
Sort

The Sort method takes any number of strings, which represent columns. Using - as a prefix will sort in descending order.

// ORDER BY name ASC
rem.Use[Accounts]().Sort("name").All(db)

// ORDER BY name DESC
rem.Use[Accounts]().Sort("-name").All(db)

// ORDER BY name ASC, id DESC
rem.Use[Accounts]().Sort("name", "-id").All(db)
SQL All

Executes a raw SQL query with parameters and returns a list of records.

accounts, err := rem.Use[Accounts]().SqlAll(db, "select * from accounts where id >= ?", 100)
// accounts []*Accounts

accounts, err := rem.Use[Accounts]().SqlAllToMap(db, "select * from accounts where id >= ?", 100)
// accounts []map[string]interface{}
Table Column Add

The TableColumnAdd method adds a column to a table. A field must exist in the model struct for the column to be added.

type Accounts struct {
	Id      int64  `db:"id" db_primary:"true"`
	Name    string `db:"name"`
	IsAdmin bool   `db:"is_admin"`
}

_, err := rem.Use[Accounts]().TableColumnAdd(db, "is_admin")
Table Column Drop

The TableColumnDrop method drops a column to a table.

_, err := rem.Use[Accounts]().TableColumnDrop(db, "is_admin")
Table Create

The TableCreate method creates a table for the model.

_, err := rem.Use[Accounts]().TableCreate(db)

// Override the table name.
_, err := rem.Use[Accounts](rem.Config{Table: "users"}).TableCreate(db)

// Only create the table if it doesn't exist.
_, err := rem.Use[Accounts]().TableCreate(db, rem.TableCreateConfig{IfNotExists: true})
Table Drop

The TableDrop method drops a table for the model.

_, err := rem.Use[Accounts]().TableDrop(db)

// Override the table name.
_, err := rem.Use[Accounts](rem.Config{Table: "users"}).TableDrop(db)

// Only drop the table if it exists.
_, err := rem.Use[Accounts]().TableDrop(db, rem.TableDropConfig{IfExists: true})
To Map

The ToMap convenience method converts a model pointer into a map[string]interface{}. Keys on the returned map are column names.

Note: Zero-valued primary keys are excluded from the returned map.

Note: Fields that implement the driver.Valuer interface are converted to their Value() representation. For example, a sql.NullString will be converted to either string or nil.

account := &Accounts{
    Id:   100,
    Name: "New Name",
}

data := rem.Use[Accounts]().ToMap(account)
// data map[string]interface{}
Transaction

REM supports transactions via the Transaction(*sql.Tx) method.

tx, _ := db.Begin()

_, err := rem.Use[Accounts]().
	Filter("id", "=", 100).
	Transaction(tx).
	Delete(db)

if err != nil {
	tx.Rollback()
	panic(err)
}

err = tx.Commit()
if err != nil {
	panic(err)
}
Update

The Update method updates matching records.

The first argument is a *sql.DB instance.

The second argument is a pointer to the updated record.

The third argument is a spread of columns to update. If no columns are provided, the update will fail. This minor annoyance is by design and is intended to ensure that column updates are intentional.

account := &Accounts{
	Id:   200,
	Name: "New Name",
}

// The `name` column will be updated, but `id` won't.
results, err := rem.Use[Accounts]().
	Filter("id", "=", 100).
	Update(db, account, "name")

// results sql.Result

REM also provides a UpdateMap convenience method that updates matching records with all columns provided by a map[string]interface{}.

account := map[string]interface{}{
	"name": "New Name",
}

results, err := rem.Use[Accounts]().
	Filter("id", "=", 100).
	UpdateMap(db, account)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MigrateDown

func MigrateDown(db *sql.DB, migrations []Migration) ([]string, error)

func MigrateUp

func MigrateUp(db *sql.DB, migrations []Migration) ([]string, error)

func SetDialect

func SetDialect(dialect Dialect)

Types

type Config

type Config struct {
	Table string
}

type Dialect

type Dialect interface {
	BuildDelete(QueryConfig) (string, []interface{}, error)
	BuildInsert(QueryConfig, map[string]interface{}, ...string) (string, []interface{}, error)
	BuildSelect(QueryConfig) (string, []interface{}, error)
	BuildTableColumnAdd(QueryConfig, string) (string, error)
	BuildTableColumnDrop(QueryConfig, string) (string, error)
	BuildTableCreate(QueryConfig, TableCreateConfig) (string, error)
	BuildTableDrop(QueryConfig, TableDropConfig) (string, error)
	BuildUpdate(QueryConfig, map[string]interface{}, ...string) (string, []interface{}, error)
	ColumnType(reflect.StructField) (string, error)
	Param(i int) string
	QuoteIdentifier(string) string
}

type DialectStringer

type DialectStringer interface {
	StringForDialect(Dialect) string
}

type DialectStringerWithArgs

type DialectStringerWithArgs interface {
	StringWithArgs(Dialect, []interface{}) (string, []interface{}, error)
}

type FilterClause

type FilterClause struct {
	Left     interface{}
	Operator string
	Right    interface{}
	Rule     string
}

func And

func And(clauses ...interface{}) []FilterClause

func Exists

func Exists(value interface{}) FilterClause

func NotExists

func NotExists(value interface{}) FilterClause

func Or

func Or(clauses ...interface{}) []FilterClause

func Q

func Q(column interface{}, operator string, value interface{}) FilterClause

func (FilterClause) StringWithArgs

func (filter FilterClause) StringWithArgs(dialect Dialect, args []interface{}) (string, []interface{}, error)

type ForeignKey

type ForeignKey[To any] struct {
	Row   *To
	Valid bool
}

func (*ForeignKey[To]) Fetch

func (fk *ForeignKey[To]) Fetch(db *sql.DB) (*To, error)

func (*ForeignKey[To]) Model

func (fk *ForeignKey[To]) Model() *Model[To]

func (*ForeignKey[To]) Query

func (fk *ForeignKey[To]) Query() *Query[To]

type JoinClause

type JoinClause struct {
	Direction string
	On        []FilterClause
	Table     string
}

type Migration

type Migration interface {
	Down(db *sql.DB) error
	Up(db *sql.DB) error
}

type MigrationLogs

type MigrationLogs struct {
	CreatedAt     time.Time `db:"created_at"`
	Direction     string    `db:"direction" db_max_length:"10"`
	Id            int64     `db:"id" db_primary:"true"`
	MigrationType string    `db:"migration_type" db_max_length:"255"`
}

type Model

type Model[T any] struct {
	Fields        map[string]reflect.StructField
	PrimaryColumn string
	PrimaryField  string
	Table         string
	Type          reflect.Type
}

func Register

func Register[T any](configs ...Config) *Model[T]

func Use

func Use[T any](configs ...Config) *Model[T]

func (*Model[T]) All

func (model *Model[T]) All(db *sql.DB) ([]*T, error)

func (*Model[T]) AllToMap

func (model *Model[T]) AllToMap(db *sql.DB) ([]map[string]interface{}, error)

func (*Model[T]) Context

func (model *Model[T]) Context(context context.Context) *Query[T]

func (*Model[T]) Count

func (model *Model[T]) Count(db *sql.DB) (uint, error)

func (*Model[T]) Dialect

func (model *Model[T]) Dialect(dialect Dialect) *Query[T]

func (*Model[T]) FetchRelated

func (model *Model[T]) FetchRelated(columns ...string) *Query[T]

func (*Model[T]) Filter

func (model *Model[T]) Filter(column interface{}, operator string, value interface{}) *Query[T]

func (*Model[T]) FilterAnd

func (model *Model[T]) FilterAnd(clauses ...interface{}) *Query[T]

func (*Model[T]) FilterOr

func (model *Model[T]) FilterOr(clauses ...interface{}) *Query[T]

func (*Model[T]) Insert

func (model *Model[T]) Insert(db *sql.DB, row *T) (sql.Result, error)

func (*Model[T]) InsertMap

func (model *Model[T]) InsertMap(db *sql.DB, data map[string]interface{}) (sql.Result, error)

func (*Model[T]) Query

func (model *Model[T]) Query() *Query[T]

func (*Model[T]) Scan

func (model *Model[T]) Scan(rows *sql.Rows) (*T, error)

func (*Model[T]) ScanMap

func (model *Model[T]) ScanMap(data map[string]interface{}) (*T, error)

func (*Model[T]) ScanToMap

func (model *Model[T]) ScanToMap(rows *sql.Rows) (map[string]interface{}, error)

func (*Model[T]) Select

func (model *Model[T]) Select(columns ...interface{}) *Query[T]

func (*Model[T]) Sort

func (model *Model[T]) Sort(columns ...string) *Query[T]

func (*Model[T]) SqlAll

func (model *Model[T]) SqlAll(db *sql.DB, sql string, args ...interface{}) ([]*T, error)

func (*Model[T]) SqlAllToMap

func (model *Model[T]) SqlAllToMap(db *sql.DB, sql string, args ...interface{}) ([]map[string]interface{}, error)

func (*Model[T]) TableColumnAdd

func (model *Model[T]) TableColumnAdd(db *sql.DB, column string) (sql.Result, error)

func (*Model[T]) TableColumnDrop

func (model *Model[T]) TableColumnDrop(db *sql.DB, column string) (sql.Result, error)

func (*Model[T]) TableCreate

func (model *Model[T]) TableCreate(db *sql.DB, tableCreateConfig ...TableCreateConfig) (sql.Result, error)

func (*Model[T]) TableDrop

func (model *Model[T]) TableDrop(db *sql.DB, tableDropConfig ...TableDropConfig) (sql.Result, error)

func (*Model[T]) ToMap

func (model *Model[T]) ToMap(row *T) (map[string]interface{}, error)

func (*Model[T]) Transaction

func (model *Model[T]) Transaction(transaction *sql.Tx) *Query[T]

type NullForeignKey

type NullForeignKey[To any] struct {
	Row   *To
	Valid bool
}

func (*NullForeignKey[To]) Fetch

func (fk *NullForeignKey[To]) Fetch(db *sql.DB) (*To, error)

func (*NullForeignKey[To]) Model

func (fk *NullForeignKey[To]) Model() *Model[To]

func (*NullForeignKey[To]) Query

func (fk *NullForeignKey[To]) Query() *Query[To]

type OneToMany

type OneToMany[To any] struct {
	RelatedColumn string
	RowPk         interface{}
	Rows          []*To
}

func (*OneToMany[To]) All

func (field *OneToMany[To]) All(db *sql.DB) ([]*To, error)

func (*OneToMany[To]) Model

func (field *OneToMany[To]) Model() *Model[To]

func (*OneToMany[To]) Query

func (field *OneToMany[To]) Query() *Query[To]

type Query

type Query[T any] struct {
	Config QueryConfig
	Error  error
	Model  *Model[T]
	Rows   *sql.Rows
	// contains filtered or unexported fields
}

func (*Query[T]) All

func (query *Query[T]) All(db *sql.DB) ([]*T, error)

func (*Query[T]) AllToMap

func (query *Query[T]) AllToMap(db *sql.DB) ([]map[string]interface{}, error)

func (*Query[T]) Context

func (query *Query[T]) Context(context context.Context) *Query[T]

func (*Query[T]) Count

func (query *Query[T]) Count(db *sql.DB) (uint, error)

func (*Query[T]) Delete

func (query *Query[T]) Delete(db *sql.DB) (sql.Result, error)

func (*Query[T]) Dialect

func (query *Query[T]) Dialect(dialect Dialect) *Query[T]

func (*Query[T]) Exists

func (query *Query[T]) Exists(db *sql.DB) (bool, error)

func (*Query[T]) FetchRelated

func (query *Query[T]) FetchRelated(columns ...string) *Query[T]

func (*Query[T]) Filter

func (query *Query[T]) Filter(column interface{}, operator string, value interface{}) *Query[T]

func (*Query[T]) FilterAnd

func (query *Query[T]) FilterAnd(clauses ...interface{}) *Query[T]

func (*Query[T]) FilterOr

func (query *Query[T]) FilterOr(clauses ...interface{}) *Query[T]

func (*Query[T]) First

func (query *Query[T]) First(db *sql.DB) (*T, error)

func (*Query[T]) FirstToMap

func (query *Query[T]) FirstToMap(db *sql.DB) (map[string]interface{}, error)

func (*Query[T]) Insert

func (query *Query[T]) Insert(db *sql.DB, row *T) (sql.Result, error)

func (*Query[T]) InsertMap

func (query *Query[T]) InsertMap(db *sql.DB, data map[string]interface{}) (sql.Result, error)

func (*Query[T]) Join

func (query *Query[T]) Join(table string, clauses ...interface{}) *Query[T]

func (*Query[T]) JoinFull

func (query *Query[T]) JoinFull(table string, clauses ...interface{}) *Query[T]

func (*Query[T]) JoinLeft

func (query *Query[T]) JoinLeft(table string, clauses ...interface{}) *Query[T]

func (*Query[T]) JoinRight

func (query *Query[T]) JoinRight(table string, clauses ...interface{}) *Query[T]

func (*Query[T]) Limit

func (query *Query[T]) Limit(limit interface{}) *Query[T]

func (*Query[T]) Offset

func (query *Query[T]) Offset(offset interface{}) *Query[T]

func (*Query[T]) Select

func (query *Query[T]) Select(columns ...interface{}) *Query[T]

func (*Query[T]) Sort

func (query *Query[T]) Sort(columns ...string) *Query[T]

func (Query[T]) StringWithArgs

func (query Query[T]) StringWithArgs(dialect Dialect, args []interface{}) (string, []interface{}, error)

func (*Query[T]) TableColumnAdd

func (query *Query[T]) TableColumnAdd(db *sql.DB, column string) (sql.Result, error)

func (*Query[T]) TableColumnDrop

func (query *Query[T]) TableColumnDrop(db *sql.DB, column string) (sql.Result, error)

func (*Query[T]) TableCreate

func (query *Query[T]) TableCreate(db *sql.DB, tableCreateConfig ...TableCreateConfig) (sql.Result, error)

func (*Query[T]) TableDrop

func (query *Query[T]) TableDrop(db *sql.DB, tableDropConfig ...TableDropConfig) (sql.Result, error)

func (*Query[T]) Transaction

func (query *Query[T]) Transaction(transaction *sql.Tx) *Query[T]

func (*Query[T]) Update

func (query *Query[T]) Update(db *sql.DB, row *T, columns ...string) (sql.Result, error)

func (*Query[T]) UpdateMap

func (query *Query[T]) UpdateMap(db *sql.DB, data map[string]interface{}) (sql.Result, error)

type QueryConfig

type QueryConfig struct {
	Count        bool
	Context      context.Context
	FetchRelated []string
	Fields       map[string]reflect.StructField
	Filters      []FilterClause
	Joins        []JoinClause
	Limit        interface{}
	Offset       interface{}
	Params       []interface{}
	Selected     []interface{}
	Sort         []string
	Table        string
	Transaction  *sql.Tx
}

type SqlAs

type SqlAs struct {
	Alias  string
	Column interface{}
}

func As

func As(column interface{}, alias string) SqlAs

func (SqlAs) StringForDialect

func (as SqlAs) StringForDialect(dialect Dialect) string

type SqlColumn

type SqlColumn string

func Column

func Column(column string) SqlColumn

func (SqlColumn) StringForDialect

func (column SqlColumn) StringForDialect(dialect Dialect) string

type SqlParam

type SqlParam struct {
	Value interface{}
}

func Param

func Param(value interface{}) SqlParam

type SqlUnsafe

type SqlUnsafe struct {
	Sql string
}

func Unsafe

func Unsafe(sql string) SqlUnsafe

func (SqlUnsafe) String

func (sqlUnsafe SqlUnsafe) String() string

type SqlWithParams

type SqlWithParams struct {
	Segments []interface{}
}

func Sql

func Sql(segments ...interface{}) SqlWithParams

func (SqlWithParams) StringWithArgs

func (sqlWithParams SqlWithParams) StringWithArgs(dialect Dialect, args []interface{}) (string, []interface{}, error)

type TableCreateConfig

type TableCreateConfig struct {
	IfNotExists bool
}

type TableDropConfig

type TableDropConfig struct {
	IfExists bool
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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