From b6d581e57c7acf86278c5ccb8a2d11fb5ec7d413 Mon Sep 17 00:00:00 2001 From: Zuo Wang Date: Sat, 22 Jun 2024 10:07:48 +0800 Subject: [PATCH] examples/docker --- examples/docker/Dockerfile | 0 examples/docker/db/errors.go | 7 + .../migrations/20240621224741_auto.down.sql | 1 + .../db/migrations/20240621224741_auto.up.sql | 1 + examples/docker/db/post.go | 73 ++++ examples/docker/db/post_query.go | 317 ++++++++++++++++++ examples/docker/db/queryx.go | 120 +++++++ examples/docker/db/queryx/adapter.go | 36 ++ examples/docker/db/queryx/bigint.go | 75 +++++ examples/docker/db/queryx/bigint_column.go | 93 +++++ examples/docker/db/queryx/bind.go | 167 +++++++++ examples/docker/db/queryx/clause.go | 57 ++++ examples/docker/db/queryx/config.go | 24 ++ examples/docker/db/queryx/datetime.go | 115 +++++++ examples/docker/db/queryx/datetime_column.go | 97 ++++++ examples/docker/db/queryx/db.go | 110 ++++++ examples/docker/db/queryx/delete.go | 36 ++ examples/docker/db/queryx/env.go | 7 + examples/docker/db/queryx/insert.go | 82 +++++ examples/docker/db/queryx/logger.go | 15 + examples/docker/db/queryx/post_change.go | 80 +++++ examples/docker/db/queryx/scan.go | 135 ++++++++ examples/docker/db/queryx/schema.go | 43 +++ examples/docker/db/queryx/select.go | 142 ++++++++ examples/docker/db/queryx/string.go | 72 ++++ examples/docker/db/queryx/string_column.go | 93 +++++ examples/docker/db/queryx/table.go | 13 + examples/docker/db/queryx/update.go | 67 ++++ examples/docker/go.mod | 5 + examples/docker/go.sum | 2 + examples/docker/main.go | 26 ++ examples/docker/schema.hcl | 18 + website/.vitepress/config.mts | 1 + website/docs/docker-support.md | 1 + 34 files changed, 2131 insertions(+) create mode 100644 examples/docker/Dockerfile create mode 100755 examples/docker/db/errors.go create mode 100644 examples/docker/db/migrations/20240621224741_auto.down.sql create mode 100644 examples/docker/db/migrations/20240621224741_auto.up.sql create mode 100755 examples/docker/db/post.go create mode 100755 examples/docker/db/post_query.go create mode 100755 examples/docker/db/queryx.go create mode 100755 examples/docker/db/queryx/adapter.go create mode 100755 examples/docker/db/queryx/bigint.go create mode 100755 examples/docker/db/queryx/bigint_column.go create mode 100755 examples/docker/db/queryx/bind.go create mode 100755 examples/docker/db/queryx/clause.go create mode 100755 examples/docker/db/queryx/config.go create mode 100755 examples/docker/db/queryx/datetime.go create mode 100755 examples/docker/db/queryx/datetime_column.go create mode 100755 examples/docker/db/queryx/db.go create mode 100755 examples/docker/db/queryx/delete.go create mode 100755 examples/docker/db/queryx/env.go create mode 100755 examples/docker/db/queryx/insert.go create mode 100755 examples/docker/db/queryx/logger.go create mode 100755 examples/docker/db/queryx/post_change.go create mode 100755 examples/docker/db/queryx/scan.go create mode 100755 examples/docker/db/queryx/schema.go create mode 100755 examples/docker/db/queryx/select.go create mode 100755 examples/docker/db/queryx/string.go create mode 100755 examples/docker/db/queryx/string_column.go create mode 100755 examples/docker/db/queryx/table.go create mode 100755 examples/docker/db/queryx/update.go create mode 100644 examples/docker/go.mod create mode 100644 examples/docker/go.sum create mode 100644 examples/docker/main.go create mode 100644 examples/docker/schema.hcl create mode 100644 website/docs/docker-support.md diff --git a/examples/docker/Dockerfile b/examples/docker/Dockerfile new file mode 100644 index 00000000..e69de29b diff --git a/examples/docker/db/errors.go b/examples/docker/db/errors.go new file mode 100755 index 00000000..a74d06ff --- /dev/null +++ b/examples/docker/db/errors.go @@ -0,0 +1,7 @@ +// Code generated by queryx, DO NOT EDIT. + +package db + +import "errors" + +var ErrInsertAllEmptyChanges = errors.New("queryx: insert all with empty changes") diff --git a/examples/docker/db/migrations/20240621224741_auto.down.sql b/examples/docker/db/migrations/20240621224741_auto.down.sql new file mode 100644 index 00000000..13dafbfe --- /dev/null +++ b/examples/docker/db/migrations/20240621224741_auto.down.sql @@ -0,0 +1 @@ +DROP TABLE `posts` \ No newline at end of file diff --git a/examples/docker/db/migrations/20240621224741_auto.up.sql b/examples/docker/db/migrations/20240621224741_auto.up.sql new file mode 100644 index 00000000..e9628b61 --- /dev/null +++ b/examples/docker/db/migrations/20240621224741_auto.up.sql @@ -0,0 +1 @@ +CREATE TABLE `posts` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `title` varchar NULL, `content` text NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL); \ No newline at end of file diff --git a/examples/docker/db/post.go b/examples/docker/db/post.go new file mode 100755 index 00000000..10e180dd --- /dev/null +++ b/examples/docker/db/post.go @@ -0,0 +1,73 @@ +// Code generated by queryx, DO NOT EDIT. + +package db + +import ( + "fmt" + "strings" + + "docker/db/queryx" +) + +type Post struct { + ID int64 `json:"id" db:"id"` + Title queryx.String `json:"title" db:"title"` + Content queryx.String `json:"content" db:"content"` + CreatedAt queryx.Datetime `json:"createdAt" db:"created_at"` + UpdatedAt queryx.Datetime `json:"updatedAt" db:"updated_at"` + + schema *queryx.Schema + queries Queries +} + +// String implements the stringer interface. +func (p *Post) String() string { + var sb strings.Builder + sb.WriteString("(Post ") + sb.WriteString(fmt.Sprintf("id: %v", p.ID)) + sb.WriteString(", ") + sb.WriteString(fmt.Sprintf("title: %s", p.Title)) + sb.WriteString(", ") + sb.WriteString(fmt.Sprintf("content: %s", p.Content)) + sb.WriteString(", ") + sb.WriteString(fmt.Sprintf("created_at: %v", p.CreatedAt)) + sb.WriteString(", ") + sb.WriteString(fmt.Sprintf("updated_at: %v", p.UpdatedAt)) + sb.WriteString(")") + return sb.String() +} + +func (p *Post) applyChange(change *queryx.PostChange) error { + if change == nil { + return nil + } + if change.ID.Set { + p.ID = change.ID.Val + } + if change.Title.Set { + p.Title = change.Title + } + if change.Content.Set { + p.Content = change.Content + } + if change.CreatedAt.Set { + p.CreatedAt = change.CreatedAt + } + if change.UpdatedAt.Set { + p.UpdatedAt = change.UpdatedAt + } + return nil +} +func (p *Post) Update(change *queryx.PostChange) error { + _, err := p.queries.QueryPost().Where(p.schema.And(p.schema.PostID.EQ(p.ID))).UpdateAll(change) + if err != nil { + return err + } + + return p.applyChange(change) +} + +func (p *Post) Delete() error { + _, err := p.queries.QueryPost().Delete(p.ID) + return err +} diff --git a/examples/docker/db/post_query.go b/examples/docker/db/post_query.go new file mode 100755 index 00000000..4a76a97e --- /dev/null +++ b/examples/docker/db/post_query.go @@ -0,0 +1,317 @@ +// Code generated by queryx, DO NOT EDIT. + +package db + +import ( + "database/sql" + "errors" + "fmt" + + "docker/db/queryx" +) + +type PostQuery struct { + adapter *queryx.Adapter + schema *queryx.Schema + queries Queries + selectStatement *queryx.SelectStatement + preload map[string]bool + err error +} + +func NewPostQuery(adapter *queryx.Adapter, schema *queryx.Schema, queries Queries) *PostQuery { + s := queryx.NewSelect().Select("posts.*").From("posts") + return &PostQuery{ + adapter: adapter, + schema: schema, + queries: queries, + selectStatement: s, + preload: make(map[string]bool), + } +} + +func (q *PostQuery) Create(change *queryx.PostChange) (*Post, error) { + if q.err != nil { + return nil, q.err + } + + record := &Post{ + schema: q.schema, + queries: q.queries, + } + now := queryx.Now("2006-01-02 15:04:05.000") + if !change.CreatedAt.Set { + change.SetCreatedAt(now) + } + if !change.UpdatedAt.Set { + change.SetUpdatedAt(now) + } + columns, values := change.Changes() + query, args := queryx.NewInsert(). + Into("posts"). + Columns(columns...). + Values(values...). + Returning("*").ToSQL() + err := q.adapter.QueryOne(query, args...).Scan(record) + if err != nil { + return nil, err + } + + return record, nil +} + +func (q *PostQuery) InsertAll(changes []*queryx.PostChange) (int64, error) { + if q.err != nil { + return 0, q.err + } + + if len(changes) == 0 { + return 0, ErrInsertAllEmptyChanges + } + now := queryx.Now("2006-01-02 15:04:05.000") + for _, change := range changes { + if !change.CreatedAt.Set { + change.SetCreatedAt(now) + } + if !change.UpdatedAt.Set { + change.SetUpdatedAt(now) + } + } + + s := queryx.NewInsert().Into("posts") + for i, change := range changes { + columns, values := change.Changes() + if i == 0 { + s.Columns(columns...) + } + s.Values(values...) + } + + query, args := s.ToSQL() + return q.adapter.Exec(query, args...) +} + +func (q *PostQuery) Delete(id int64) (int64, error) { + query, args := queryx.NewDelete().From("posts").Where(q.schema.PostID.EQ(id)).ToSQL() + result, err := q.adapter.Exec(query, args...) + if err != nil { + return 0, err + } + return result, err +} + +func (q *PostQuery) Find(id int64) (*Post, error) { + res, err := q.Where(q.schema.PostID.EQ(id)).First() + if err != nil { + return nil, err + } + if res == nil { + return nil, sql.ErrNoRows + } + res.schema = q.schema + res.queries = q.queries + return res, err +} + +func (q *PostQuery) FindBy(where *queryx.Clause) (*Post, error) { + return q.Where(where).First() +} + +func (q *PostQuery) FindBySQL(query string, args ...interface{}) ([]*Post, error) { + var postList []Post + posts := make([]*Post, 0) + err := q.adapter.Query(query, args...).Scan(&postList) + if err != nil { + return nil, err + } + for i := 0; i < len(postList); i++ { + posts = append(posts, &postList[i]) + } + return posts, nil +} + +func (q *PostQuery) Where(clauses ...*queryx.Clause) *PostQuery { + q.selectStatement.Where(clauses...) + return q +} + +func (q *PostQuery) Select(selection ...string) *PostQuery { + q.selectStatement.Select(selection...) + return q +} + +func (q *PostQuery) Limit(limit int) *PostQuery { + q.selectStatement.Limit(limit) + return q +} + +func (q *PostQuery) Offset(offset int) *PostQuery { + q.selectStatement.Offset(offset) + return q +} + +func (q *PostQuery) Group(group string) *PostQuery { + q.selectStatement.GroupBy(group) + return q +} + +func (q *PostQuery) Having(having string) *PostQuery { + q.selectStatement.Having(having) + return q +} + +func (q *PostQuery) Joins(joins string) *PostQuery { + q.selectStatement.Join(joins) + return q +} + +func (q *PostQuery) Order(order ...string) *PostQuery { + q.selectStatement.Order(order...) + return q +} + +func (q *PostQuery) All() ([]*Post, error) { + if q.err != nil { + return nil, q.err + } + var rows []Post + posts := make([]*Post, 0) + query, args := q.selectStatement.ToSQL() + err := q.adapter.Query(query, args...).Scan(&rows) + if err != nil { + return nil, err + } + + if len(rows) == 0 { + return posts, nil + } + + for i := range rows { + rows[i].schema = q.schema + rows[i].queries = q.queries + posts = append(posts, &rows[i]) + } + + return posts, nil +} + +func (q *PostQuery) First() (*Post, error) { + q.Limit(1) + results, err := q.All() + if err != nil { + return nil, err + } + if len(results) > 0 { + return results[0], nil + } + + return nil, nil +} + +func (q *PostQuery) Count() (int64, error) { + var res struct { + Count int64 `db:"count"` + } + q.selectStatement.Select("count(*)") + query, args := q.selectStatement.ToSQL() + if err := q.adapter.QueryOne(query, args...).Scan(&res); err != nil { + return 0, err + } + + return res.Count, nil +} + +func (q *PostQuery) Avg(v string) (float64, error) { + var res struct { + Avg sql.NullFloat64 `db:"avg"` + } + q.selectStatement.Select(fmt.Sprintf("avg(%+v)", v)) + query, args := q.selectStatement.ToSQL() + if err := q.adapter.QueryOne(query, args...).Scan(&res); err != nil { + return 0, err + } + + return res.Avg.Float64, nil +} + +func (q *PostQuery) Sum(v string) (float64, error) { + var res struct { + Sum sql.NullFloat64 `db:"sum"` + } + q.selectStatement.Select(fmt.Sprintf("sum(%+v)", v)) + query, args := q.selectStatement.ToSQL() + if err := q.adapter.QueryOne(query, args...).Scan(&res); err != nil { + return 0, err + } + + return res.Sum.Float64, nil +} + +func (q *PostQuery) Max(v string) (float64, error) { + var res struct { + Max sql.NullFloat64 `db:"max"` + } + q.selectStatement.Select(fmt.Sprintf("max(%+v)", v)) + query, args := q.selectStatement.ToSQL() + if err := q.adapter.QueryOne(query, args...).Scan(&res); err != nil { + return 0, err + } + + return res.Max.Float64, nil +} + +func (q *PostQuery) Min(v string) (float64, error) { + var res struct { + Min sql.NullFloat64 `db:"min"` + } + q.selectStatement.Select(fmt.Sprintf("min(%+v)", v)) + query, args := q.selectStatement.ToSQL() + if err := q.adapter.QueryOne(query, args...).Scan(&res); err != nil { + return 0, err + } + + return res.Min.Float64, nil +} + +func (q *PostQuery) Exists() (bool, error) { + q.selectStatement.Select("1 AS one").Limit(1) + // select 1 as one from users limit 1 + query, args := q.selectStatement.ToSQL() + var res struct { + One int64 `db:"one"` + } + if err := q.adapter.QueryOne(query, args...).Scan(&res); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return false, nil + } + return false, err + } + + return res.One == 1, nil +} + +func (q *PostQuery) UpdateAll(change *queryx.PostChange) (int64, error) { + if q.err != nil { + return 0, q.err + } + now := queryx.Now("2006-01-02 15:04:05.000") + if !change.UpdatedAt.Set { + change.SetUpdatedAt(now) + } + columns, values := change.Changes() + query, args := q.selectStatement.Update().Columns(columns...).Values(values...).ToSQL() + result, err := q.adapter.Exec(query, args...) + if err != nil { + return 0, err + } + return result, err +} + +func (q *PostQuery) DeleteAll() (int64, error) { + query, args := q.selectStatement.Delete().ToSQL() + result, err := q.adapter.Exec(query, args...) + if err != nil { + return 0, err + } + return result, err +} diff --git a/examples/docker/db/queryx.go b/examples/docker/db/queryx.go new file mode 100755 index 00000000..35ba8614 --- /dev/null +++ b/examples/docker/db/queryx.go @@ -0,0 +1,120 @@ +// Code generated by queryx, DO NOT EDIT. + +package db + +import ( + "context" + "database/sql" + "fmt" + "log" + "os" + + "docker/db/queryx" +) + +type Queries interface { + QueryPost() *PostQuery +} + +type QXClient struct { + db *sql.DB + config *queryx.Config + logger queryx.Logger + *queryx.Adapter + *queryx.Schema +} + +func NewClient() (*QXClient, error) { + env := os.Getenv("QUERYX_ENV") + if env == "" { + env = "development" + } + return NewClientWithEnv(env) +} + +func NewClientWithEnv(env string) (*QXClient, error) { + config := queryx.NewConfig(env) + if config == nil { + return nil, fmt.Errorf("client config is missing for %s", env) + } + db, err := sql.Open("sqlite3", config.URL) + if err != nil { + return nil, err + } + + client := &QXClient{ + db: db, + config: config, + Adapter: queryx.NewAdapter(db), + Schema: queryx.NewSchema(), + } + client.setDefaultLogger() + + return client, nil +} + +func (c *QXClient) SetLogger(logger queryx.Logger) { + c.logger = logger +} + +func (c *QXClient) setDefaultLogger() { + c.logger = log.New(os.Stderr, "sql ", log.Llongfile|log.LstdFlags) +} + +func (c *QXClient) QueryPost() *PostQuery { + return NewPostQuery(c.Adapter, c.Schema, c) +} + +func (c *QXClient) Transaction(f func(t *Tx) error) error { + tx, err := c.Tx() + if err != nil { + return err + } + defer tx.Rollback() + if err = f(tx); err != nil { + return err + } + return tx.Commit() +} + +func (c *QXClient) Raw(fragment string, args ...interface{}) *queryx.Clause { + return queryx.NewClause(fragment, args) +} + +type Tx struct { + *queryx.Schema + *queryx.Adapter + tx *sql.Tx + client *QXClient +} + +func (c *QXClient) Tx() (*Tx, error) { + ctx := context.Background() + tx, err := c.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + + return &Tx{ + tx: tx, + Schema: c.Schema, + Adapter: queryx.NewAdapter(tx), + client: c, + }, nil +} + +func (tx *Tx) Commit() error { + return tx.tx.Commit() +} + +func (tx *Tx) Rollback() error { + return tx.tx.Rollback() +} + +func (tx *Tx) QueryPost() *PostQuery { + return NewPostQuery(tx.Adapter, tx.Schema, tx) +} + +func (tx *Tx) Raw(fragment string, args ...interface{}) *queryx.Clause { + return queryx.NewClause(fragment, args) +} diff --git a/examples/docker/db/queryx/adapter.go b/examples/docker/db/queryx/adapter.go new file mode 100755 index 00000000..10c86739 --- /dev/null +++ b/examples/docker/db/queryx/adapter.go @@ -0,0 +1,36 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "regexp" + + _ "github.com/mattn/go-sqlite3" +) + +func (a *Adapter) Exec(query string, args ...interface{}) (int64, error) { + matched1, err := regexp.MatchString(`.* IN (.*?)`, query) + if err != nil { + return 0, err + } + matched2, err := regexp.MatchString(`.* in (.*?)`, query) + if err != nil { + return 0, err + } + if matched1 || matched2 { + query, args, err = In(query, args...) + if err != nil { + return 0, err + } + } + query, args = rebind(query, args) + result, err := a.db.Exec(query, args...) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +func rebind(query string, args []interface{}) (string, []interface{}) { + return query, args +} diff --git a/examples/docker/db/queryx/bigint.go b/examples/docker/db/queryx/bigint.go new file mode 100755 index 00000000..1d58235d --- /dev/null +++ b/examples/docker/db/queryx/bigint.go @@ -0,0 +1,75 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "strconv" +) + +type BigInt struct { + Val int64 + Valid bool + Set bool +} + +func NewBigInt(v int64) BigInt { + return BigInt{Val: v, Valid: true, Set: true} +} + +func NewNullableBigInt(v *int64) BigInt { + if v != nil { + return NewBigInt(*v) + } + return BigInt{Set: true} +} + +// Scan implements the Scanner interface. +func (b *BigInt) Scan(value interface{}) error { + n := sql.NullInt64{} + err := n.Scan(value) + if err != nil { + return err + } + b.Val, b.Valid = n.Int64, n.Valid + return nil +} + +// Value implements the driver Valuer interface. +func (b BigInt) Value() (driver.Value, error) { + if !b.Valid { + return nil, nil + } + return b.Val, nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (b BigInt) MarshalJSON() ([]byte, error) { + if !b.Valid { + return json.Marshal(nil) + } + return json.Marshal(b.Val) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (b *BigInt) UnmarshalJSON(data []byte) error { + b.Set = true + if string(data) == "null" { + return nil + } + b.Valid = true + if err := json.Unmarshal(data, &b.Val); err != nil { + return err + } + return nil +} + +// String implements the stringer interface. +func (b BigInt) String() string { + if !b.Valid { + return "null" + } + return strconv.FormatInt(b.Val, 10) +} diff --git a/examples/docker/db/queryx/bigint_column.go b/examples/docker/db/queryx/bigint_column.go new file mode 100755 index 00000000..8682bd2a --- /dev/null +++ b/examples/docker/db/queryx/bigint_column.go @@ -0,0 +1,93 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import "fmt" + +type BigIntColumn struct { + Name string + Table *Table +} + +func (t *Table) NewBigIntColumn(name string) *BigIntColumn { + return &BigIntColumn{ + Table: t, + Name: name, + } +} + +func (c *BigIntColumn) EQ(v int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s = ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) NE(v int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s <> ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) LT(v int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s < ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) GT(v int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s > ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) LE(v int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s <= ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) GE(v int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s >= ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) In(v []int64) *Clause { + if len(v) == 0 { + return &Clause{ + fragment: "1=0", + } + } + return &Clause{ + fragment: fmt.Sprintf("%s.%s IN (?)", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *BigIntColumn) InRange(start int64, end int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s >= ? and %s.%s< ?", c.Table.Name, c.Name, c.Table.Name, c.Name), + args: []interface{}{start, end}, + } +} + +func (c *BigIntColumn) Between(start int64, end int64) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s >= ? and %s.%s<= ?", c.Table.Name, c.Name, c.Table.Name, c.Name), + args: []interface{}{start, end}, + } +} + +func (c *BigIntColumn) Asc() string { + return fmt.Sprintf("%s.%s ASC", c.Table.Name, c.Name) +} + +func (c *BigIntColumn) Desc() string { + return fmt.Sprintf("%s.%s DESC", c.Table.Name, c.Name) +} diff --git a/examples/docker/db/queryx/bind.go b/examples/docker/db/queryx/bind.go new file mode 100755 index 00000000..d248e8db --- /dev/null +++ b/examples/docker/db/queryx/bind.go @@ -0,0 +1,167 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "database/sql/driver" + "fmt" + "reflect" + "strings" +) + +// In expands slice values in args, returning the modified query string +// and a new arg list that can be executed by a database. The `query` should +// use the `?` bindVar. The return value uses the `?` bindVar. +func In(query string, args ...interface{}) (string, []interface{}, error) { + // argMeta stores reflect.Value and length for slices and + // the value itself for non-slice arguments + type argMeta struct { + v reflect.Value + i interface{} + length int + } + + var flatArgsCount int + var anySlices bool + + var stackMeta [32]argMeta + + var meta []argMeta + if len(args) <= len(stackMeta) { + meta = stackMeta[:len(args)] + } else { + meta = make([]argMeta, len(args)) + } + + for i, arg := range args { + if a, ok := arg.(driver.Valuer); ok { + var err error + arg, err = a.Value() + if err != nil { + return "", nil, err + } + } + + if v, ok := asSliceForIn(arg); ok { + meta[i].length = v.Len() + meta[i].v = v + + anySlices = true + flatArgsCount += meta[i].length + + if meta[i].length == 0 { + return "", nil, fmt.Errorf("empty slice passed to 'in' query") + } + } else { + meta[i].i = arg + flatArgsCount++ + } + } + + // don't do any parsing if there aren't any slices; note that this means + // some errors that we might have caught below will not be returned. + if !anySlices { + return query, args, nil + } + + newArgs := make([]interface{}, 0, flatArgsCount) + + var buf strings.Builder + buf.Grow(len(query) + len(", ?")*flatArgsCount) + + var arg, offset int + + for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') { + if arg >= len(meta) { + // if an argument wasn't passed, lets return an error; this is + // not actually how database/sql Exec/Query works, but since we are + // creating an argument list programmatically, we want to be able + // to catch these programmer errors earlier. + return "", nil, fmt.Errorf("number of bindVars exceeds arguments") + } + + argMeta := meta[arg] + arg++ + + // not a slice, continue. + // our questionmark will either be written before the next expansion + // of a slice or after the loop when writing the rest of the query + if argMeta.length == 0 { + offset = offset + i + 1 + newArgs = append(newArgs, argMeta.i) + continue + } + + // write everything up to and including our ? character + buf.WriteString(query[:offset+i+1]) + + for si := 1; si < argMeta.length; si++ { + buf.WriteString(", ?") + } + + newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length) + + // slice the query and reset the offset. this avoids some bookkeeping for + // the write after the loop + query = query[offset+i+1:] + offset = 0 + } + + buf.WriteString(query) + + if arg < len(meta) { + return "", nil, fmt.Errorf("number of bindVars less than number arguments") + } + + return buf.String(), newArgs, nil +} +func asSliceForIn(i interface{}) (v reflect.Value, ok bool) { + if i == nil { + return reflect.Value{}, false + } + + v = reflect.ValueOf(i) + t := Deref(v.Type()) + + // Only expand slices + if t.Kind() != reflect.Slice { + return reflect.Value{}, false + } + + // []byte is a driver.Value type so it should not be expanded + if t == reflect.TypeOf([]byte{}) { + return reflect.Value{}, false + + } + + return v, true +} + +// Deref is Indirect for reflect.Types +func Deref(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} { + switch val := v.Interface().(type) { + case []interface{}: + args = append(args, val...) + case []int: + for i := range val { + args = append(args, val[i]) + } + case []string: + for i := range val { + args = append(args, val[i]) + } + default: + for si := 0; si < vlen; si++ { + args = append(args, v.Index(si).Interface()) + } + } + + return args +} diff --git a/examples/docker/db/queryx/clause.go b/examples/docker/db/queryx/clause.go new file mode 100755 index 00000000..a5b03575 --- /dev/null +++ b/examples/docker/db/queryx/clause.go @@ -0,0 +1,57 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "fmt" + "strings" +) + +type Clause struct { + fragment string + args []interface{} + err error +} + +func NewClause(fragment string, args []interface{}) *Clause { + return &Clause{ + fragment: fragment, + args: args, + } +} + +func (c *Clause) Err() error { + return c.err +} + +func (c *Clause) And(clauses ...*Clause) *Clause { + if len(clauses) == 0 { + return c + } + + var fragments []string + var args []interface{} + clauses = append([]*Clause{c}, clauses...) + for _, clause := range clauses { + fragments = append(fragments, fmt.Sprintf("(%s)", clause.fragment)) + args = append(args, clause.args...) + } + + return NewClause(strings.Join(fragments, " AND "), args) +} + +func (c *Clause) Or(clauses ...*Clause) *Clause { + if len(clauses) == 0 { + return c + } + + var fragments []string + var args []interface{} + clauses = append([]*Clause{c}, clauses...) + for _, clause := range clauses { + fragments = append(fragments, fmt.Sprintf("(%s)", clause.fragment)) + args = append(args, clause.args...) + } + + return NewClause(strings.Join(fragments, " OR "), args) +} diff --git a/examples/docker/db/queryx/config.go b/examples/docker/db/queryx/config.go new file mode 100755 index 00000000..800584ed --- /dev/null +++ b/examples/docker/db/queryx/config.go @@ -0,0 +1,24 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "strings" +) + +type Config struct { + URL string +} + +func NewConfig(env string) *Config { + switch env { + case "development": + return &Config{ + URL: fixURL("sqlite:blog_development.sqlite3"), + } + } + return nil +} +func fixURL(rawURL string) string { + return "file:" + strings.TrimPrefix(rawURL, "sqlite:") +} diff --git a/examples/docker/db/queryx/datetime.go b/examples/docker/db/queryx/datetime.go new file mode 100755 index 00000000..d3159be7 --- /dev/null +++ b/examples/docker/db/queryx/datetime.go @@ -0,0 +1,115 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "time" +) + +type Datetime struct { + Val time.Time + Valid bool + Set bool +} + +func loadLocation() (*time.Location, error) { + return time.LoadLocation("Local") +} + +func parseDatetime(s string) (*time.Time, error) { + loc, err := loadLocation() + if err != nil { + return nil, err + } + t, err := time.ParseInLocation("2006-01-02 15:04:05", s, loc) + if err != nil { + return nil, err + } + return &t, nil +} + +func Now(layout string) string { + loc, _ := loadLocation() + return time.Now().In(loc).Format(layout) +} + +func NewDatetime(v string) Datetime { + t, err := parseDatetime(v) + if err != nil { + return Datetime{Set: true} + } + return Datetime{Val: *t, Valid: true, Set: true} +} + +func NewNullableDatetime(v *string) Datetime { + if v != nil { + return NewDatetime(*v) + } + return Datetime{Set: true} +} + +// Scan implements the Scanner interface. +func (d *Datetime) Scan(value interface{}) error { + n := sql.NullTime{} + err := n.Scan(value) + if err != nil { + return err + } + d.Val, d.Valid = n.Time, n.Valid + loc, err := loadLocation() + if err != nil { + return err + } + d.Val = d.Val.In(loc) + return nil +} + +// Value implements the driver Valuer interface. +func (d Datetime) Value() (driver.Value, error) { + if !d.Valid { + return nil, nil + } + return d.Val.UTC(), nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (d Datetime) MarshalJSON() ([]byte, error) { + if !d.Valid { + return json.Marshal(nil) + } + return json.Marshal(d.Val.UTC()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *Datetime) UnmarshalJSON(data []byte) error { + d.Set = true + s := string(data) + if s == "null" || s == "" { + return nil + } + d.Valid = true + t := time.Time{} + err := t.UnmarshalJSON(data) + if err != nil { + return err + } + + location, err := loadLocation() + if err != nil { + return err + } + d.Val = t.In(location) + + return nil +} + +// String implements the stringer interface. +func (d Datetime) String() string { + if !d.Valid { + return "null" + } + return d.Val.Format("2006-01-02 15:04:05") +} diff --git a/examples/docker/db/queryx/datetime_column.go b/examples/docker/db/queryx/datetime_column.go new file mode 100755 index 00000000..66fea555 --- /dev/null +++ b/examples/docker/db/queryx/datetime_column.go @@ -0,0 +1,97 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "fmt" +) + +type DatetimeColumn struct { + Name string + Table *Table +} + +func (t *Table) NewDatetimeColumn(name string) *DatetimeColumn { + return &DatetimeColumn{ + Table: t, + Name: name, + } +} + +func (c *DatetimeColumn) EQ(v string) *Clause { + d, err := parseDatetime(v) + if err != nil { + return &Clause{ + err: err, + } + } + return &Clause{ + fragment: fmt.Sprintf("%s.%s = ?", c.Table.Name, c.Name), + args: []interface{}{d.UTC()}, + err: err, + } +} + +func (c *DatetimeColumn) LE(v string) *Clause { + d, err := parseDatetime(v) + if err != nil { + return &Clause{ + err: err, + } + } + return &Clause{ + fragment: fmt.Sprintf("%s.%s <= ?", c.Table.Name, c.Name), + args: []interface{}{d.UTC()}, + err: err, + } +} + +func (c *DatetimeColumn) LT(v string) *Clause { + d, err := parseDatetime(v) + if err != nil { + return &Clause{ + err: err, + } + } + return &Clause{ + fragment: fmt.Sprintf("%s.%s <=?", c.Table.Name, c.Name), + args: []interface{}{d.UTC()}, + err: err, + } +} + +func (c *DatetimeColumn) GE(v string) *Clause { + d, err := parseDatetime(v) + if err != nil { + return &Clause{ + err: err, + } + } + return &Clause{ + fragment: fmt.Sprintf("%s.%s >= ?", c.Table.Name, c.Name), + args: []interface{}{d.UTC()}, + err: err, + } +} + +func (c *DatetimeColumn) GT(v string) *Clause { + d, err := parseDatetime(v) + if err != nil { + return &Clause{ + err: err, + } + } + return &Clause{ + fragment: fmt.Sprintf("%s.%s > ?", c.Table.Name, c.Name), + args: []interface{}{d.UTC()}, + err: err, + } +} + +func (c *DatetimeColumn) Asc() string { + return fmt.Sprintf("%s.%s ASC", c.Table.Name, c.Name) +} + +func (c *DatetimeColumn) Desc() string { + return fmt.Sprintf("%s.%s DESC", c.Table.Name, c.Name) +} diff --git a/examples/docker/db/queryx/db.go b/examples/docker/db/queryx/db.go new file mode 100755 index 00000000..f37b7902 --- /dev/null +++ b/examples/docker/db/queryx/db.go @@ -0,0 +1,110 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "database/sql" + "regexp" +) + +type DB interface { + Exec(query string, args ...interface{}) (sql.Result, error) + Query(query string, args ...interface{}) (*sql.Rows, error) +} + +type Adapter struct { + db DB +} + +func NewAdapter(db DB) *Adapter { + return &Adapter{db: db} +} + +func (a *Adapter) Query(query string, args ...interface{}) *Rows { + return &Rows{ + adapter: a, + query: query, + args: args, + } +} + +type Rows struct { + adapter *Adapter + query string + args []interface{} + err error +} + +func (r *Rows) Scan(v interface{}) error { + if r.err != nil { + return r.err + } + var err error + query, args := r.query, r.args + matched1, err := regexp.MatchString(`.* IN (.*?)`, query) + if err != nil { + return err + } + matched2, err := regexp.MatchString(`.* in (.*?)`, query) + if err != nil { + return err + } + if matched1 || matched2 { + query, args, err = In(query, args...) + if err != nil { + return err + } + } + query, args = rebind(query, args) + rows, err := r.adapter.db.Query(query, args...) + if err != nil { + return err + } + err = ScanSlice(rows, v) + if err != nil { + return err + } + return err +} + +type Row struct { + adapter *Adapter + query string + args []interface{} +} + +func (r *Row) Scan(v interface{}) error { + query, args := r.query, r.args + matched1, err := regexp.MatchString(`.* IN (.*?)`, query) + if err != nil { + return err + } + matched2, err := regexp.MatchString(`.* in (.*?)`, query) + if err != nil { + return err + } + if matched1 || matched2 { + query, args, err = In(query, args...) + if err != nil { + return err + } + } + query, args = rebind(query, args) + rows, err := r.adapter.db.Query(query, args...) + if err != nil { + return err + } + err = ScanOne(rows, v) + if err != nil { + return err + } + return err +} + +func (a *Adapter) QueryOne(query string, args ...interface{}) *Row { + return &Row{ + adapter: a, + query: query, + args: args, + } +} diff --git a/examples/docker/db/queryx/delete.go b/examples/docker/db/queryx/delete.go new file mode 100755 index 00000000..736b99df --- /dev/null +++ b/examples/docker/db/queryx/delete.go @@ -0,0 +1,36 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import "fmt" + +type DeleteStatemnet struct { + from string + where *Clause +} + +func NewDelete() *DeleteStatemnet { + return &DeleteStatemnet{} +} + +func (s *DeleteStatemnet) From(from string) *DeleteStatemnet { + s.from = from + return s +} + +func (s *DeleteStatemnet) Where(expr *Clause) *DeleteStatemnet { + s.where = expr + return s +} + +func (s *DeleteStatemnet) ToSQL() (string, []interface{}) { + sql, args := "", []interface{}{} + sql = fmt.Sprintf("DELETE FROM %s", s.from) + + if s.where != nil { + sql = fmt.Sprintf("%s WHERE %s", sql, s.where.fragment) + args = append(args, s.where.args...) + } + + return sql, args +} diff --git a/examples/docker/db/queryx/env.go b/examples/docker/db/queryx/env.go new file mode 100755 index 00000000..79c79ec2 --- /dev/null +++ b/examples/docker/db/queryx/env.go @@ -0,0 +1,7 @@ +package queryx + +import "os" + +func getenv(k string) string { + return os.Getenv(k) +} diff --git a/examples/docker/db/queryx/insert.go b/examples/docker/db/queryx/insert.go new file mode 100755 index 00000000..cf09926d --- /dev/null +++ b/examples/docker/db/queryx/insert.go @@ -0,0 +1,82 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "fmt" + "strings" +) + +type InsertStatement struct { + into string + columns []string + values [][]interface{} + returning []string + onConflict string +} + +func NewInsert() *InsertStatement { + return &InsertStatement{} +} + +func (s *InsertStatement) Into(into string) *InsertStatement { + s.into = into + return s +} + +func (s *InsertStatement) Columns(columns ...string) *InsertStatement { + s.columns = columns + return s +} + +func (s *InsertStatement) Values(values ...interface{}) *InsertStatement { + if len(values) > 0 { + s.values = append(s.values, values) + } + return s +} +func (s *InsertStatement) Returning(returning ...string) *InsertStatement { + s.returning = returning + return s +} + +func (s *InsertStatement) OnConflict(onConflict string) *InsertStatement { + s.onConflict = onConflict + return s +} + +func (s *InsertStatement) ToSQL() (string, []interface{}) { + sql := fmt.Sprintf("INSERT INTO %s", s.into) + + if len(s.columns) > 0 { + sql = fmt.Sprintf("%s(%s)", sql, strings.Join(s.columns, ", ")) + } else { + sql = fmt.Sprintf("%s DEFAULT VALUES", sql) + } + values := []string{} + for _, v := range s.values { + ss := []string{} + for range v { + ss = append(ss, "?") + } + values = append(values, fmt.Sprintf("(%s)", strings.Join(ss, ", "))) + } + if len(values) > 0 { + sql = fmt.Sprintf("%s VALUES %s", sql, strings.Join(values, ", ")) + } + + if len(s.returning) > 0 { + sql = fmt.Sprintf("%s RETURNING %s", sql, strings.Join(s.returning, ", ")) + } + + if s.onConflict != "" { + sql = fmt.Sprintf("%s %s", sql, s.onConflict) + } + + args := []interface{}{} + for _, v := range s.values { + args = append(args, v...) + } + + return sql, args +} diff --git a/examples/docker/db/queryx/logger.go b/examples/docker/db/queryx/logger.go new file mode 100755 index 00000000..8d0fe04a --- /dev/null +++ b/examples/docker/db/queryx/logger.go @@ -0,0 +1,15 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +type Logger interface { + Print(v ...interface{}) + Printf(format string, v ...interface{}) + Println(v ...interface{}) + Panic(v ...interface{}) + Panicf(format string, v ...interface{}) + Panicln(v ...interface{}) + Fatal(v ...interface{}) + Fatalf(format string, v ...interface{}) + Fatalln(v ...interface{}) +} diff --git a/examples/docker/db/queryx/post_change.go b/examples/docker/db/queryx/post_change.go new file mode 100755 index 00000000..3962645f --- /dev/null +++ b/examples/docker/db/queryx/post_change.go @@ -0,0 +1,80 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +type PostChange struct { + ID BigInt + Title String + Content String + CreatedAt Datetime + UpdatedAt Datetime +} + +func (c *PostChange) Changes() (columns []string, values []interface{}) { + if c == nil { + return columns, values + } + if c.ID.Set { + columns = append(columns, "id") + values = append(values, c.ID) + } + if c.Title.Set { + columns = append(columns, "title") + values = append(values, c.Title) + } + if c.Content.Set { + columns = append(columns, "content") + values = append(values, c.Content) + } + if c.CreatedAt.Set { + columns = append(columns, "created_at") + values = append(values, c.CreatedAt) + } + if c.UpdatedAt.Set { + columns = append(columns, "updated_at") + values = append(values, c.UpdatedAt) + } + return columns, values +} + +func (c *PostChange) SetID(id int64) *PostChange { + c.ID = NewBigInt(id) + c.ID.Set = true + return c +} + +func (c *PostChange) SetTitle(title string) *PostChange { + c.Title = NewString(title) + c.Title.Set = true + return c +} + +func (c *PostChange) SetNullableTitle(title *string) *PostChange { + c.Title = NewNullableString(title) + c.Title.Set = true + return c +} + +func (c *PostChange) SetContent(content string) *PostChange { + c.Content = NewString(content) + c.Content.Set = true + return c +} + +func (c *PostChange) SetNullableContent(content *string) *PostChange { + c.Content = NewNullableString(content) + c.Content.Set = true + return c +} + +func (c *PostChange) SetCreatedAt(createdAt string) *PostChange { + c.CreatedAt = NewDatetime(createdAt) + c.CreatedAt.Set = true + return c +} + +func (c *PostChange) SetUpdatedAt(updatedAt string) *PostChange { + c.UpdatedAt = NewDatetime(updatedAt) + c.UpdatedAt.Set = true + return c +} diff --git a/examples/docker/db/queryx/scan.go b/examples/docker/db/queryx/scan.go new file mode 100755 index 00000000..4dde2a11 --- /dev/null +++ b/examples/docker/db/queryx/scan.go @@ -0,0 +1,135 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "database/sql" + "fmt" + "reflect" + "strings" +) + +func ScanOne(rows *sql.Rows, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + return fmt.Errorf("must pass pointer") + } + + columns, err := rows.Columns() + if err != nil { + return err + } + + typ := rv.Type().Elem() + + // column -> index + names := make(map[string]int, typ.NumField()) + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + names[columnName(f)] = i + } + + if !rows.Next() { + return sql.ErrNoRows + } + + indexes := make(map[int]int, typ.NumField()) + for i, c := range columns { + name := strings.ToLower(strings.Split(c, "(")[0]) + index, ok := names[name] + if !ok { + return fmt.Errorf("name not found") + } + indexes[i] = index + } + + values := make([]interface{}, len(columns)) + for i := range columns { + t := typ.Field(indexes[i]).Type + values[i] = reflect.New(t).Interface() + + } + + // scan into interfaces + if err := rows.Scan(values...); err != nil { + return err + } + + for i, v := range values { + reflect.Indirect(rv).Field(indexes[i]).Set(reflect.Indirect(reflect.ValueOf(v))) + } + + if rows.Next() { + return fmt.Errorf("more than one row") + } + + return nil +} + +func ScanSlice(rows *sql.Rows, v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + return fmt.Errorf("must pass pointer") + } + + rv = reflect.Indirect(rv) + if k := rv.Kind(); k != reflect.Slice { + return fmt.Errorf("must pass slice") + } + + columns, err := rows.Columns() + if err != nil { + return err + } + + typ := rv.Type().Elem() + + names := make(map[string]int, typ.NumField()) + for i := 0; i < typ.NumField(); i++ { + f := typ.Field(i) + names[columnName(f)] = i + } + + indexes := make(map[int]int, typ.NumField()) + for i, c := range columns { + name := strings.ToLower(strings.Split(c, "(")[0]) + index, ok := names[name] + if !ok { + return fmt.Errorf("name %+v not found", name) + } + indexes[i] = index + } + + for rows.Next() { + values := make([]interface{}, len(columns)) + for i := range columns { + t := typ.Field(indexes[i]).Type + values[i] = reflect.New(t).Interface() + } + + // scan into interfaces + if err := rows.Scan(values...); err != nil { + return err + } + + // convert to reflect.Value + e := reflect.New(typ).Elem() + for i, v := range values { + fv := e.Field(indexes[i]) + fv.Set(reflect.Indirect(reflect.ValueOf(v))) + } + + vv := reflect.Append(rv, e) + rv.Set(vv) + } + + return nil +} + +func columnName(f reflect.StructField) string { + name := strings.ToLower(f.Name) + if tag, ok := f.Tag.Lookup("db"); ok { + name = tag + } + return name +} diff --git a/examples/docker/db/queryx/schema.go b/examples/docker/db/queryx/schema.go new file mode 100755 index 00000000..352bc339 --- /dev/null +++ b/examples/docker/db/queryx/schema.go @@ -0,0 +1,43 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +type Schema struct { + Post *Table + PostID *BigIntColumn + PostTitle *StringColumn + PostContent *StringColumn + PostCreatedAt *DatetimeColumn + PostUpdatedAt *DatetimeColumn +} + +func NewSchema() *Schema { + post := NewTable("posts") + + return &Schema{ + Post: post, + PostID: post.NewBigIntColumn("id"), + PostTitle: post.NewStringColumn("title"), + PostContent: post.NewStringColumn("content"), + PostCreatedAt: post.NewDatetimeColumn("created_at"), + PostUpdatedAt: post.NewDatetimeColumn("updated_at"), + } +} + +func (s *Schema) And(clauses ...*Clause) *Clause { + return clauses[0].And(clauses[1:]...) +} + +func (s *Schema) Or(clauses ...*Clause) *Clause { + return clauses[0].Or(clauses[1:]...) +} + +func (s *Schema) ChangePost() *PostChange { + return &PostChange{ + ID: BigInt{}, + Title: String{}, + Content: String{}, + CreatedAt: Datetime{}, + UpdatedAt: Datetime{}, + } +} diff --git a/examples/docker/db/queryx/select.go b/examples/docker/db/queryx/select.go new file mode 100755 index 00000000..fec8eeb8 --- /dev/null +++ b/examples/docker/db/queryx/select.go @@ -0,0 +1,142 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "fmt" + "strings" +) + +type SelectStatement struct { + selection []string + from string + where *Clause + limit *int + offset *int + order []string + group string + having string + distinct []string + joins []string +} + +func NewSelect() *SelectStatement { + return &SelectStatement{} +} + +func (s *SelectStatement) Select(selection ...string) *SelectStatement { + s.selection = selection + return s +} + +func (s *SelectStatement) From(from string) *SelectStatement { + s.from = from + return s +} + +func (s *SelectStatement) Where(clauses ...*Clause) *SelectStatement { + if len(clauses) == 0 { + return s + } + + if s.where == nil { + s.where = clauses[0].And(clauses[1:]...) + } else { + s.where = s.where.And(clauses...) + } + + return s +} + +func (s *SelectStatement) Limit(limit int) *SelectStatement { + s.limit = &limit + return s +} + +func (s *SelectStatement) Offset(offset int) *SelectStatement { + s.offset = &offset + return s +} + +func (s *SelectStatement) Order(order ...string) *SelectStatement { + s.order = append(s.order, order...) + return s +} + +func (s *SelectStatement) Distinct(distinct ...string) *SelectStatement { + s.distinct = distinct + return s +} + +func (s *SelectStatement) GroupBy(group string) *SelectStatement { + s.group = group + return s +} + +func (s *SelectStatement) Having(having string) *SelectStatement { + s.having = having + return s +} + +func (s *SelectStatement) Join(join ...string) *SelectStatement { + s.joins = append(s.joins, join...) + return s +} + +// convert select statement to update statement +func (s *SelectStatement) Update() *UpdateStatement { + if s.limit != nil { + return NewUpdate().Table(s.from).Where(s.where) // TODO: convert into subquery + } + return NewUpdate().Table(s.from).Where(s.where) +} + +// convert select statement to delete statement +func (s *SelectStatement) Delete() *DeleteStatemnet { + return NewDelete().From(s.from).Where(s.where) +} + +func (s *SelectStatement) ToSQL() (string, []interface{}) { + var query string + if len(s.distinct) > 0 { + query = fmt.Sprintf("SELECT DISTINCT %s FROM %s", strings.Join(s.distinct, ", "), s.from) + } else { + query = fmt.Sprintf("SELECT %s FROM %s", strings.Join(s.selection, ", "), s.from) + } + args := []interface{}{} + + if len(s.joins) > 0 { + for i := 0; i < len(s.joins); i++ { + query = fmt.Sprintf("%s %s", query, s.joins[i]) + } + } + + if s.where != nil { + query = fmt.Sprintf("%s WHERE %s", query, s.where.fragment) + args = append(args, s.where.args...) + } + + if s.group != "" { + query = fmt.Sprintf("%s GROUP BY %s", query, s.group) + } + + if s.having != "" { + query = fmt.Sprintf("%s HAVING %s", query, s.having) + } + + if s.order != nil { + query = fmt.Sprintf("%s ORDER BY %s", query, strings.Join(s.order, ", ")) + } + + if s.limit != nil { + query = fmt.Sprintf("%s LIMIT ?", query) + args = append(args, *s.limit) + } + + if s.offset != nil { + query = fmt.Sprintf("%s OFFSET ?", query) + args = append(args, *s.offset) + } + + return query, args +} diff --git a/examples/docker/db/queryx/string.go b/examples/docker/db/queryx/string.go new file mode 100755 index 00000000..d47dd756 --- /dev/null +++ b/examples/docker/db/queryx/string.go @@ -0,0 +1,72 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" +) + +type String struct { + Val string + Valid bool + Set bool +} + +func NewString(v string) String { + return String{Val: v, Valid: true, Set: true} +} + +func NewNullableString(v *string) String { + if v != nil { + return NewString(*v) + } + return String{Set: true} +} + +// Scan implements the Scanner interface. +func (s *String) Scan(value interface{}) error { + ns := sql.NullString{String: s.Val} + err := ns.Scan(value) + s.Val, s.Valid = ns.String, ns.Valid + return err +} + +// Value implements the driver Valuer interface. +func (s String) Value() (driver.Value, error) { + if !s.Valid { + return nil, nil + } + return s.Val, nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (s String) MarshalJSON() ([]byte, error) { + if !s.Valid { + return json.Marshal(nil) + } + return json.Marshal(s.Val) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *String) UnmarshalJSON(data []byte) error { + s.Set = true + if string(data) == "null" { + return nil + } + s.Valid = true + if err := json.Unmarshal(data, &s.Val); err != nil { + return err + } + return nil +} + +// String implements the stringer interface. +func (s String) String() string { + if !s.Valid { + return "null" + } + return fmt.Sprintf(`"%s"`, s.Val) +} diff --git a/examples/docker/db/queryx/string_column.go b/examples/docker/db/queryx/string_column.go new file mode 100755 index 00000000..00601c96 --- /dev/null +++ b/examples/docker/db/queryx/string_column.go @@ -0,0 +1,93 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import "fmt" + +type StringColumn struct { + Name string + Table *Table +} + +func (t *Table) NewStringColumn(name string) *StringColumn { + return &StringColumn{ + Table: t, + Name: name, + } +} + +func (c *StringColumn) NE(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s <> ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} +func (c *StringColumn) EQ(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("lower(%s.%s) = lower(?)", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *StringColumn) IEQ(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s = ?", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *StringColumn) In(v []string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s IN (?)", c.Table.Name, c.Name), + args: []interface{}{v}, + } +} + +func (c *StringColumn) Like(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s ilike ?", c.Table.Name, c.Name), + args: []interface{}{fmt.Sprintf("%s%s%s", "%", v, "%")}, + } +} + +func (c *StringColumn) ILike(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s like ?", c.Table.Name, c.Name), + args: []interface{}{fmt.Sprintf("%s%s%s", "%", v, "%")}, + } +} +func (c *StringColumn) IStartsWith(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s like ?", c.Table.Name, c.Name), + args: []interface{}{fmt.Sprintf("%s%s", v, "%")}, + } +} + +func (c *StringColumn) StartsWith(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s ilike ?", c.Table.Name, c.Name), + args: []interface{}{fmt.Sprintf("%s%s", v, "%")}, + } +} + +func (c *StringColumn) EndsWith(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s ilike ?", c.Table.Name, c.Name), + args: []interface{}{fmt.Sprintf("%s%s", "%", v)}, + } +} + +func (c *StringColumn) IEndsWith(v string) *Clause { + return &Clause{ + fragment: fmt.Sprintf("%s.%s like ?", c.Table.Name, c.Name), + args: []interface{}{fmt.Sprintf("%s%s", "%", v)}, + } +} + +func (c *StringColumn) Asc() string { + return fmt.Sprintf("%s.%s ASC", c.Table.Name, c.Name) +} + +func (c *StringColumn) Desc() string { + return fmt.Sprintf("%s.%s DESC", c.Table.Name, c.Name) +} diff --git a/examples/docker/db/queryx/table.go b/examples/docker/db/queryx/table.go new file mode 100755 index 00000000..75a2e9a8 --- /dev/null +++ b/examples/docker/db/queryx/table.go @@ -0,0 +1,13 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +type Table struct { + Name string +} + +func NewTable(name string) *Table { + return &Table{ + Name: name, + } +} diff --git a/examples/docker/db/queryx/update.go b/examples/docker/db/queryx/update.go new file mode 100755 index 00000000..2388cfbe --- /dev/null +++ b/examples/docker/db/queryx/update.go @@ -0,0 +1,67 @@ +// Code generated by queryx, DO NOT EDIT. + +package queryx + +import ( + "fmt" + "strings" +) + +type UpdateStatement struct { + table string + columns []string + values []interface{} + where *Clause + returning string +} + +func NewUpdate() *UpdateStatement { + return &UpdateStatement{} +} + +func (s *UpdateStatement) Table(table string) *UpdateStatement { + s.table = table + return s +} + +func (s *UpdateStatement) Columns(columns ...string) *UpdateStatement { + s.columns = columns + return s +} + +func (s *UpdateStatement) Values(values ...interface{}) *UpdateStatement { + s.values = values + return s +} + +func (s *UpdateStatement) Where(expr *Clause) *UpdateStatement { + s.where = expr + return s +} + +func (s *UpdateStatement) Returning(returning string) *UpdateStatement { + s.returning = returning + return s +} + +func (s *UpdateStatement) ToSQL() (string, []interface{}) { + sql, args := fmt.Sprintf("UPDATE %s SET", s.table), s.values + + sets := []string{} + for _, col := range s.columns { + sets = append(sets, fmt.Sprintf("%s = ?", col)) + } + + sql = fmt.Sprintf("%s %s", sql, strings.Join(sets, ", ")) + + if s.where != nil { + sql = fmt.Sprintf("%s WHERE %s", sql, s.where.fragment) + args = append(args, s.where.args...) + } + + if s.returning != "" { + sql = fmt.Sprintf("%s RETURNING %s", sql, s.returning) + } + + return sql, args +} diff --git a/examples/docker/go.mod b/examples/docker/go.mod new file mode 100644 index 00000000..fe6d02c9 --- /dev/null +++ b/examples/docker/go.mod @@ -0,0 +1,5 @@ +module docker + +go 1.21.9 + +require github.com/mattn/go-sqlite3 v1.14.22 diff --git a/examples/docker/go.sum b/examples/docker/go.sum new file mode 100644 index 00000000..e8d092a9 --- /dev/null +++ b/examples/docker/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/examples/docker/main.go b/examples/docker/main.go new file mode 100644 index 00000000..e8bb78fe --- /dev/null +++ b/examples/docker/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "log" + + "docker/db" +) + +func main() { + c, err := db.NewClient() + if err != nil { + log.Fatal(err) + } + + type Row struct { + Version string `db:"version"` + } + var row Row + err = c.QueryOne("select sqlite_version() as version").Scan(&row) + if err != nil { + log.Fatal(err) + } + + fmt.Println(row.Version) +} diff --git a/examples/docker/schema.hcl b/examples/docker/schema.hcl new file mode 100644 index 00000000..fd7e85f0 --- /dev/null +++ b/examples/docker/schema.hcl @@ -0,0 +1,18 @@ +database "db" { + adapter = "sqlite" + + config "development" { + url = "sqlite:blog_development.sqlite3" + } + + generator "client-golang" {} + + model "Post" { + column "title" { + type = string + } + column "content" { + type = text + } + } +} diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts index 12be7d7d..052e95ba 100644 --- a/website/.vitepress/config.mts +++ b/website/.vitepress/config.mts @@ -133,6 +133,7 @@ function sidebarDocs(): DefaultTheme.SidebarItem[] { { text: "Custom Primary Key", link: "custom-primary-key" }, { text: "Custom Database Time Zone", link: "time-zone" }, { text: "Build from source", link: "build-from-source" }, + { text: "Docker Support", link: "docker-support" }, ], }, { diff --git a/website/docs/docker-support.md b/website/docs/docker-support.md new file mode 100644 index 00000000..5a25400f --- /dev/null +++ b/website/docs/docker-support.md @@ -0,0 +1 @@ +# Docker Support