sqlike

package module
v2.0.0-...-d0150a3 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2023 License: MIT Imports: 28 Imported by: 0

README ΒΆ

sqlike

Build Release Go Report Go Coverage LICENSE FOSSA Status

A Golang SQL ORM which anti-toxic query and focus on the latest features.

πŸ”¨ Installation

go get github.com/si3nloong/sqlike/v2

Fully compatible with native library database/sql, which mean you are allow to use driver.Valuer and sql.Scanner.

πŸ“» Legacy Support

SQLike did support mysql 5.7 as well. For better compatibility, we suggest you to use at least mysql 8.0.

πŸͺ£ Minimum Requirements

  • mysql 8.0 and above
  • golang 1.18 and above

❓ Why another ORM?

  • We don't really care about legacy support, we want latest feature that mysql and golang offer us
  • We want to get rid from toxic query (also known as slow query)

✨ What do we provide apart from native package (database/sql)?

  • Support UUID (^8.0)
  • Support ENUM and SET
  • Support JSON
  • Support descending index (^8.0)
  • Support multi-valued index (^8.0.17)
  • Support Spatial with package orb, such as Point, LineString
  • Support generated column of stored column and virtual column
  • Extra custom type such as Date, Key, Boolean
  • Support struct on Find, FindOne, InsertOne, Insert, ModifyOne, DeleteOne, Delete, DestroyOne and Paginate apis
  • Support Transactions
  • Support offset based and cursor based pagination
  • Support advance and complex query statement
  • Support civil.Date, civil.Time and time.Location
  • Support language.Tag and currency.Unit
  • Support authorization plugin Casbin
  • Support tracing plugin OpenTracing
  • Developer friendly, (query is highly similar to native SQL query)
  • Support sqldump for backup purpose (experiment)

⚠️ Limitation

Our main objective is anti toxic query, that why some functionality we doesn't offer out of box

  • Offset based pagination (but you may achieve this by using Limit and Offset)
  • Eager loading (we want to avoid magic function, you should handle this by your own using goroutines)
  • Left wildcard search using Like is not allow but you may use expr.Raw to bypass it
  • Bidirectional sorting is not allow except MySQL 8.0 and above

General APIs

package main

import (
    "time"
    "github.com/si3nloong/sqlike/sqlike/actions"
    "github.com/si3nloong/sqlike/sqlike"
    "github.com/si3nloong/sqlike/sqlike/options"
    "github.com/si3nloong/sqlike/sql/expr"
    "github.com/google/uuid"
    "context"

    _ "github.com/go-sql-driver/mysql"
)

// UserStatus :
type UserStatus string

const (
    UserStatusActive  UserStatus = "ACTIVE"
    UserStatusSuspend UserStatus = "SUSPEND"
)

type User struct {
    ID        uuid.UUID  `sqlike:",primary_key"`
    ICNo      string     `sqlike:",generated_column"` // generated column generated by virtual column `Detail.ICNo`
    Name      string     `sqlike:",size=200,charset=latin1"` // you can set the data type length and charset with struct tag
    Email     string     `sqlike:",unique"` // set to unique
    Address   string     `sqlike:",longtext"` // `longtext` is an alias of long text data type in mysql
    Detail    struct {
        ICNo    string `sqlike:",virtual_column=ICNo"` // virtual column
        PhoneNo string
        Age     uint
    }
    Status    UserStatus `sqlike:",enum=ACTIVE|SUSPEND"` // enum data type
    CreatedAt time.Time
    UpdatedAt time.Time
}

func newUser() (user User) {
    now := time.Now()
    user.ID = uuid.New()
    user.CreatedAt = now
    user.UpdatedAt = now
    return
}

func main() {
    ctx := context.Background()
    client := sqlike.MustConnect(
        ctx,
        "mysql",
        options.Connect().
        SetUsername("root").
        SetPassword("").
        SetHost("localhost").
        SetPort("3306"),
    )

    client.SetPrimaryKey("ID") // Change default primary key name
    version := client.Version() // Display driver version
    dbs, _ := client.ListDatabases(ctx) // List databases

    userTable := client.Database("sqlike").Table("User")

    // Drop Table
    userTable.Drop(ctx)

    // Migrate Table
    userTable.Migrate(ctx, User{})

    // Truncate Table
    userTable.Truncate(ctx)

    // Insert one record
    {
        user := newUser()
        if _, err := userTable.InsertOne(ctx, &user); err != nil {
            panic(err)
        }
    }

    // Insert multiple record
    {
        users := [...]User{
            newUser(),
            newUser(),
            newUser(),
        }
        if _, err := userTable.Insert(ctx, &users); err != nil {
            panic(err)
        }
    }

    // Find one record
    {
        user := User{}
        err := userTable.FindOne(ctx, nil).Decode(&user)
        if err != nil {
            // `sqlike.ErrNoRows` is an alias of `sql.ErrNoRows`
            if err != sqlike.ErrNoRows {  // or you may check with sql.ErrNoRows
                panic(err)
            }
            // record not exist
        }
    }

    // Find multiple records
    {
        users := make([]User, 0)
        result, err := userTable.Find(
            ctx,
            actions.Find().
                Where(
                    expr.Equal("ID", result.ID),
                ).
                OrderBy(
                    expr.Desc("UpdatedAt"),
                ),
        )
        if err != nil {
            panic(err)
        }
        // map into the struct of slice
        if err:= result.All(&users); err != nil {
            panic(err)
        }
    }

    // Update one record with all fields of struct
    {
        user.Name = `πŸ€– Hello World!`
        if err := userTable.ModifyOne(ctx, &user); err != nil {
            panic(err)
        }
    }

    // Update one record with selected fields
    {
        userTable.UpdateOne(
            ctx,
            actions.UpdateOne().
                Where(
                    expr.Equal("ID", 100),
                ).Set(
                    expr.ColumnValue("Name", "SianLoong"),
                    expr.ColumnValue("Email", "test@gmail.com"),
                ),
            options.UpdateOne().SetDebug(true), // debug the query
        )
    }

    {
        limit := uint(10)
        pg, err := userTable.Paginate(
            ctx,
            actions.Paginate().
                OrderBy(
                    expr.Desc("CreatedAt"),
                ).
                Limit(limit + 1),
             options.Paginate().SetDebug(true),
        )
        if err != nil {
            panic(err)
        }

        for {
            var users []User
            if err := pg.All(&users); err != nil {
                panic(err)
            }
            length := uint(len(users))
            if length == 0 {
                break
            }
            cursor := users[length-1].ID
            if err := pg.After(ctx, cursor); err != nil {
                if err == sqlike.ErrInvalidCursor {
                    break
                }
                panic(err)
            }
            if length <= limit {
                break
            }
        }

    }
}

Inspired by gorm, mongodb-go-driver and sqlx.

πŸŽ‰ Big Thanks To

Thanks to these awesome companies for their support of Open Source developers ❀

GitHub NPM

🧲 Adopters

πŸ“„ License

MIT

Copyright (c) 2019-present, SianLoong Lee

FOSSA Status

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	ErrNoValueUpdate = errors.New("sqlike: no value to update")
	// ErrInvalidInput :
	ErrInvalidInput = errors.New("sqlike: invalid input <nil>")
	// ErrUnaddressableEntity :
	ErrUnaddressableEntity = errors.New("sqlike: unaddressable entity")
	// ErrNilEntity :
	ErrNilEntity = errors.New("sqlike: entity is <nil>")
	// ErrNoColumn :
	ErrNoColumn = errors.New("sqlike: no columns to create index")
	// ErrNestedTransaction :
	ErrNestedTransaction = errors.New("sqlike: nested transaction")
	// ErrNoRecordAffected :
	ErrNoRecordAffected = errors.New("sqlike: no record affected")
	// ErrExpectedStruct :
	ErrExpectedStruct = errors.New("sqlike: expected struct as a source")
	// ErrEmptyFields :
	ErrEmptyFields = errors.New("sqlike: empty fields")
	// ErrInvalidCursor :
	ErrInvalidCursor = errors.New("sqlike: invalid cursor")
)

errors : common error of sqlike

View Source
var EOF = io.EOF

EOF : is an alias for end of file

View Source
var ErrNoRows = sql.ErrNoRows

ErrNoRows : is an alias for no record found

Functions ΒΆ

This section is empty.

Types ΒΆ

type Client ΒΆ

type Client struct {
	*DriverInfo
	*sql.DB
	// contains filtered or unexported fields
}

Client : sqlike client is a client embedded with *sql.DB, so you may use any apis of *sql.DB

func Connect ΒΆ

func Connect(ctx context.Context, driver string, opt *options.ConnectOptions) (client *Client, err error)

Connect : connect and ping the sql server, throw error when unable to ping

func ConnectDB ΒΆ

func ConnectDB(ctx context.Context, driver string, conn driver.Connector) (*Client, error)

ConnectDB :

func MustConnect ΒΆ

func MustConnect(ctx context.Context, driver string, opt *options.ConnectOptions) *Client

MustConnect will panic if cannot connect to sql server

func MustConnectDB ΒΆ

func MustConnectDB(ctx context.Context, driver string, conn driver.Connector) *Client

MustConnectDB :

func MustOpen ΒΆ

func MustOpen(ctx context.Context, driver string, opt *options.ConnectOptions) *Client

MustOpen : must open will panic if it cannot establish a connection to sql server

func Open ΒΆ

func Open(ctx context.Context, driver string, opt *options.ConnectOptions) (client *Client, err error)

Open : open connection to sql server with connection string

func (*Client) CreateDatabase ΒΆ

func (c *Client) CreateDatabase(ctx context.Context, name string) error

CreateDatabase : create database with name

func (*Client) Database ΒΆ

func (c *Client) Database(name string) *Database

Database : this api will execute `USE database`, which will point your current connection to selected database

func (*Client) DropDatabase ΒΆ

func (c *Client) DropDatabase(ctx context.Context, name string) error

DropDatabase : drop the selected database

func (*Client) ListDatabases ΒΆ

func (c *Client) ListDatabases(ctx context.Context) ([]string, error)

ListDatabases : list all the database on current connection

func (*Client) SetLogger ΒΆ

func (c *Client) SetLogger(logger db.Logger) *Client

SetLogger : this is to set the logger for debugging, it will panic if the logger input is nil

func (*Client) SetPrimaryKey ΒΆ

func (c *Client) SetPrimaryKey(pk string) *Client

SetPrimaryKey : this will set a default primary key for subsequent operation such as Insert, InsertOne, ModifyOne

func (*Client) SetStructMapper ΒΆ

func (c *Client) SetStructMapper(mapper reflext.StructMapper) *Client

SetStructMapper : StructMapper is a mapper to reflect a struct on runtime and provide struct info

type Column ΒΆ

type Column struct {
	// column name
	Name string

	// column position in sql database
	Position int

	// column data type with precision or size, eg. VARCHAR(20)
	Type string

	// column data type without precision and size, eg. VARCHAR
	DataType string

	// whether column is nullable or not
	IsNullable types.Boolean

	// default value of the column
	DefaultValue *string

	// text character set encoding
	Charset *string

	// text collation for sorting
	Collation *string

	// column comment
	Comment string

	// extra information
	Extra string
}

Column : contains sql column information

type ColumnView ΒΆ

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

ColumnView :

func (*ColumnView) DropOne ΒΆ

func (cv *ColumnView) DropOne(ctx context.Context, name string) error

DropOne : drop column with name

func (*ColumnView) List ΒΆ

func (cv *ColumnView) List(ctx context.Context) ([]Column, error)

List : list all the column from current table

func (*ColumnView) Rename ΒΆ

func (cv *ColumnView) Rename(ctx context.Context, oldColName, newColName string) error

Rename : rename your column name

type Database ΒΆ

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

Database :

func (*Database) BeginTransaction ΒΆ

func (db *Database) BeginTransaction(ctx context.Context, opts ...*sql.TxOptions) (*Transaction, error)

BeginTransaction :

func (*Database) BuildIndexes ΒΆ

func (db *Database) BuildIndexes(ctx context.Context, paths ...string) error

BuildIndexes :

func (*Database) Exec ΒΆ

func (db *Database) Exec(ctx context.Context, query string, args ...any) (sql.Result, error)

Exec :

func (*Database) Name ΒΆ

func (db *Database) Name() string

Name : to get current database name

func (*Database) Query ΒΆ

func (db *Database) Query(ctx context.Context, query string, args ...any) (*sql.Rows, error)

Query :

func (*Database) QueryRow ΒΆ

func (db *Database) QueryRow(ctx context.Context, query string, args ...any) *Row

QueryRow :

func (*Database) QueryStmt ΒΆ

func (db *Database) QueryStmt(ctx context.Context, query any) (*Rows, error)

QueryStmt :

func (*Database) RunInTransaction ΒΆ

func (db *Database) RunInTransaction(ctx context.Context, cb txCallback, opts ...*options.TransactionOptions) error

RunInTransaction :

func (*Database) Table ΒΆ

func (db *Database) Table(name string) *Table

Table : use the table under this database

type DriverInfo ΒΆ

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

DriverInfo :

func (*DriverInfo) Charset ΒΆ

func (d *DriverInfo) Charset() charset.Code

Charset :

func (*DriverInfo) Collate ΒΆ

func (d *DriverInfo) Collate() string

Collate :

func (*DriverInfo) DriverName ΒΆ

func (d *DriverInfo) DriverName() string

DriverName :

func (*DriverInfo) Version ΒΆ

func (d *DriverInfo) Version() *semver.Version

Version :

type Index ΒΆ

type Index struct {
	Name     string
	Type     string
	IsUnique bool
}

Index :

type IndexView ΒΆ

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

IndexView :

func (*IndexView) Create ΒΆ

func (idv *IndexView) Create(ctx context.Context, idxs []sql.Index) error

Create : create multiple indexes on the table.

func (*IndexView) CreateIfNotExists ΒΆ

func (idv *IndexView) CreateIfNotExists(ctx context.Context, idxs []sql.Index) error

CreateIfNotExists :

func (*IndexView) CreateOne ΒΆ

func (idv *IndexView) CreateOne(ctx context.Context, idx sql.Index) error

CreateOne : create single index on the table.

func (*IndexView) CreateOneIfNotExists ΒΆ

func (idv *IndexView) CreateOneIfNotExists(ctx context.Context, idx sql.Index) error

CreateOneIfNotExists :

func (*IndexView) DropAll ΒΆ

func (idv *IndexView) DropAll(ctx context.Context) error

DropAll :

func (*IndexView) DropOne ΒΆ

func (idv *IndexView) DropOne(ctx context.Context, name string) error

DropOne :

func (*IndexView) List ΒΆ

func (idv *IndexView) List(ctx context.Context) ([]Index, error)

List :

type Paginator ΒΆ

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

Paginator :

func (*Paginator) After ΒΆ

func (pg *Paginator) After(ctx context.Context, cursor any) (err error)

After is to set after cursor.

func (*Paginator) All ΒΆ

func (pg *Paginator) All(dest any) error

All :

func (*Paginator) Before ΒΆ

func (pg *Paginator) Before(ctx context.Context, cursor any) (err error)

Before is to set before cursor.

type Result ΒΆ

type Result interface {
	Scan(dests ...any) error
	Columns() []string
	Next() bool
	NextResultSet() bool
	Close() error
}

Result :

type Row ΒΆ

type Row struct {
	*sql.Row
}

Row is the result of calling QueryRow to select a single row.

func (*Row) Decode ΒΆ

func (r *Row) Decode(dst any) error

Decode will unmarshal the values to struct.

type Rows ΒΆ

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

Rows :

func (*Rows) All ΒΆ

func (r *Rows) All(results any) error

All : this will map all the records from sql to a slice of struct.

func (*Rows) Close ΒΆ

func (r *Rows) Close() error

Close :

func (*Rows) ColumnTypes ΒΆ

func (r *Rows) ColumnTypes() ([]*sql.ColumnType, error)

ColumnTypes :

func (*Rows) Columns ΒΆ

func (r *Rows) Columns() []string

Columns :

func (*Rows) Decode ΒΆ

func (r *Rows) Decode(dst any) error

Decode will decode the current document into val, this will only accepting pointer of struct as an input.

func (*Rows) Error ΒΆ

func (r *Rows) Error() error

Error :

func (*Rows) Next ΒΆ

func (r *Rows) Next() bool

Next :

func (*Rows) NextResultSet ΒΆ

func (r *Rows) NextResultSet() bool

NextResultSet :

func (*Rows) Scan ΒΆ

func (r *Rows) Scan(dests ...any) error

Scan : will behave as similar as sql.Scan.

func (*Rows) ScanSlice ΒΆ

func (r *Rows) ScanSlice(results any) error

ScanSlice :

type SingleResult ΒΆ

type SingleResult interface {
	Scan(dest ...any) error
	Decode(dest any) error
	Columns() []string
	ColumnTypes() ([]*sql.ColumnType, error)
	Error() error
}

SingleResult : single result is an interface implementing apis as similar as driver.Result

type Table ΒΆ

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

Table :

func (*Table) Columns ΒΆ

func (tb *Table) Columns() *ColumnView

Columns :

func (*Table) Delete ΒΆ

func (tb *Table) Delete(
	ctx context.Context,
	act actions.DeleteStatement,
	opts ...*options.DeleteOptions,
) (int64, error)

Delete : delete multiple record on the table using where clause. If you didn't provided any where clause, it will throw error. For multiple record deletion without where clause, you should use `Truncate` instead.

func (*Table) DeleteOne ΒΆ

func (tb *Table) DeleteOne(ctx context.Context, act actions.DeleteOneStatement, opts ...*options.DeleteOneOptions) (int64, error)

DeleteOne : delete single record on the table using where clause.

func (*Table) DestroyOne ΒΆ

func (tb *Table) DestroyOne(
	ctx context.Context,
	delete any,
	opts ...*options.DestroyOneOptions,
) error

DestroyOne : hard delete a record on the table using primary key. You should alway have primary key defined in your struct in order to use this api.

func (*Table) Drop ΒΆ

func (tb *Table) Drop(ctx context.Context) (err error)

Drop : drop the table, but it might throw error when the table is not exists

func (*Table) DropIfExists ΒΆ

func (tb *Table) DropIfExists(ctx context.Context) (err error)

DropIfExists : will drop the table only if it exists.

func (*Table) Exists ΒΆ

func (tb *Table) Exists(ctx context.Context) bool

Exists : this will return true when the table exists in the database

func (*Table) Find ΒΆ

func (tb *Table) Find(
	ctx context.Context,
	act actions.SelectStatement,
	opts ...*options.FindOptions,
) (*Rows, error)

Find : find multiple records on the table.

func (*Table) FindOne ΒΆ

FindOne : find single record on the table, you should alway check the return error to ensure it have result return.

func (*Table) HasIndexByName ΒΆ

func (tb *Table) HasIndexByName(ctx context.Context, name string) (bool, error)

HasIndexByName :

func (*Table) Indexes ΒΆ

func (tb *Table) Indexes() *IndexView

Indexes :

func (*Table) Insert ΒΆ

func (tb *Table) Insert(
	ctx context.Context,
	src any,
	opts ...*options.InsertOptions,
) (sql.Result, error)

Insert : insert multiple records. You should always pass in the address of the slice.

func (*Table) InsertOne ΒΆ

func (tb *Table) InsertOne(
	ctx context.Context,
	src any,
	opts ...*options.InsertOneOptions,
) (sql.Result, error)

InsertOne : insert single record. You should always pass in the address of input.

func (*Table) ListColumns ΒΆ

func (tb *Table) ListColumns(ctx context.Context) ([]Column, error)

ListColumns : list all the column of the table.

func (*Table) ListIndexes ΒΆ

func (tb *Table) ListIndexes(ctx context.Context) ([]Index, error)

ListIndexes : list all the index of the table.

func (*Table) Migrate ΒΆ

func (tb *Table) Migrate(ctx context.Context, entity any) error

Migrate : migrate will create a new table follows by the definition of struct tag, alter when the table already exists

func (*Table) ModifyOne ΒΆ

func (tb *Table) ModifyOne(
	ctx context.Context,
	update any,
	opts ...*options.ModifyOneOptions,
) (int64, error)

ModifyOne :

func (Table) MustMigrate ΒΆ

func (tb Table) MustMigrate(ctx context.Context, entity any)

MustMigrate : this will ensure the migrate is complete, otherwise it will panic

func (*Table) MustUnsafeMigrate ΒΆ

func (tb *Table) MustUnsafeMigrate(ctx context.Context, entity any)

MustUnsafeMigrate : this will panic if it get error on unsafe migrate

func (*Table) Paginate ΒΆ

func (tb *Table) Paginate(
	ctx context.Context,
	act actions.PaginateStatement,
	opts ...*options.PaginateOptions,
) (*Paginator, error)

Paginate :

func (*Table) Rename ΒΆ

func (tb *Table) Rename(ctx context.Context, name string) error

Rename : rename the current table name to new table name

func (*Table) Replace ΒΆ

func (tb *Table) Replace(ctx context.Context, fields []string, query *sql.SelectStmt) error

Replace :

func (*Table) ReplaceOne ΒΆ

func (tb *Table) ReplaceOne(
	ctx context.Context,
	src any,
	opts ...*options.InsertOneOptions,
) (sql.Result, error)

ReplaceOne :

func (*Table) Truncate ΒΆ

func (tb *Table) Truncate(ctx context.Context) (err error)

Truncate : delete all the table data.

func (*Table) UnsafeDrop ΒΆ

func (tb *Table) UnsafeDrop(ctx context.Context) (err error)

UnsafeDrop : drop the table without table is exists and foreign key constraint error

func (*Table) UnsafeMigrate ΒΆ

func (tb *Table) UnsafeMigrate(ctx context.Context, entity any) error

UnsafeMigrate : unsafe migration will delete non-exist index and columns, beware when you use this

func (*Table) Update ΒΆ

func (tb *Table) Update(
	ctx context.Context,
	act actions.UpdateStatement,
	opts ...*options.UpdateOptions,
) (int64, error)

Update :

func (*Table) UpdateOne ΒΆ

func (tb *Table) UpdateOne(
	ctx context.Context,
	act actions.UpdateOneStatement,
	opts ...*options.UpdateOneOptions,
) (int64, error)

UpdateOne :

type Transaction ΒΆ

type Transaction struct {
	// transaction context
	context.Context
	// contains filtered or unexported fields
}

Transaction :

func (*Transaction) Commit ΒΆ

func (tx *Transaction) Commit() error

Commit : Commit commits the transaction.

func (*Transaction) Rollback ΒΆ

func (tx *Transaction) Rollback() error

Rollback : Rollback aborts the transaction.

func (*Transaction) Value ΒΆ

func (tx *Transaction) Value(key any) any

Value :

Jump to

Keyboard shortcuts

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