Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb6a45732f | |||
| 2551e07b3e | |||
| 9837fb1e37 | |||
| 12d6fface6 | |||
| 325103e8ef | |||
| 6f5748d3d3 | |||
| b25f9367ed | |||
| 8750f3ad95 | |||
| ad1faf2056 |
7
Makefile
7
Makefile
@@ -1,4 +1,9 @@
|
|||||||
|
.PHONY: run bench-select test
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go run ./cmd -o ./example/db ./example/schema.sql
|
go run ./cmd -o ./playground/db ./playground/schema.sql
|
||||||
bench-select:
|
bench-select:
|
||||||
go test ./example -bench BenchmarkSelect -memprofile memprofile.out -cpuprofile profile.out
|
go test ./example -bench BenchmarkSelect -memprofile memprofile.out -cpuprofile profile.out
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test ./playground
|
||||||
|
|||||||
@@ -76,8 +76,18 @@ func generate(scheamPath, outDir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(")")
|
sb.WriteString(")")
|
||||||
|
|
||||||
|
sb.WriteString(`
|
||||||
|
func DerivedTable(tblName string, fromQry pgm.Query) pgm.Table {
|
||||||
|
t := pgm.Table{
|
||||||
|
Name: tblName,
|
||||||
|
DerivedTable: fromQry,
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}`)
|
||||||
|
|
||||||
// Format code before saving
|
// Format code before saving
|
||||||
code, err := formatGoCode(sb.String())
|
code, err := formatGoCode(sb.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -95,6 +105,8 @@ func writeColFile(tblName string, cols []*Column, outDir string, caser cases.Cas
|
|||||||
sb.WriteString(fmt.Sprintf("package %s\n\n", filepath.Base(outDir)))
|
sb.WriteString(fmt.Sprintf("package %s\n\n", filepath.Base(outDir)))
|
||||||
sb.WriteString(fmt.Sprintf("import %q\n\n", "code.patial.tech/go/pgm"))
|
sb.WriteString(fmt.Sprintf("import %q\n\n", "code.patial.tech/go/pgm"))
|
||||||
sb.WriteString("const (")
|
sb.WriteString("const (")
|
||||||
|
sb.WriteString("\n // All fields in table " + tblName)
|
||||||
|
sb.WriteString(fmt.Sprintf("\n All pgm.Field = %q", tblName+".*"))
|
||||||
var name string
|
var name string
|
||||||
for _, c := range cols {
|
for _, c := range cols {
|
||||||
name = strings.ReplaceAll(c.Name, "_", " ")
|
name = strings.ReplaceAll(c.Name, "_", " ")
|
||||||
|
|||||||
205
pgm.go
205
pgm.go
@@ -1,170 +1,103 @@
|
|||||||
// Patial Tech.
|
// pgm
|
||||||
// Author, Ankit Patial
|
//
|
||||||
|
// A simple PG string query builder
|
||||||
|
//
|
||||||
|
// Author: Ankit Patial
|
||||||
|
|
||||||
package pgm
|
package pgm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Table in database
|
var (
|
||||||
type Table struct {
|
poolPGX atomic.Pointer[pgxpool.Pool]
|
||||||
Name string
|
ErrConnStringMissing = errors.New("connection string is empty")
|
||||||
PK []string
|
)
|
||||||
FieldCount uint16
|
|
||||||
debug bool
|
// Errors
|
||||||
|
var (
|
||||||
|
ErrInitTX = errors.New("failed to init db.tx")
|
||||||
|
ErrCommitTX = errors.New("failed to commit db.tx")
|
||||||
|
ErrNoRows = errors.New("no data found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ConnString string
|
||||||
|
MaxConns int32
|
||||||
|
MinConns int32
|
||||||
|
MaxConnLifetime time.Duration
|
||||||
|
MaxConnIdleTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug when set true will print generated query string in stdout
|
// InitPool will create new pgxpool.Pool and will keep it for its working
|
||||||
func (t Table) Debug() Clause {
|
func InitPool(conf Config) {
|
||||||
t.debug = true
|
if conf.ConnString == "" {
|
||||||
return t
|
panic(ErrConnStringMissing)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
cfg, err := pgxpool.ParseConfig(conf.ConnString)
|
||||||
// Field ==>
|
if err != nil {
|
||||||
//
|
panic(err)
|
||||||
|
|
||||||
// Field related to a table
|
|
||||||
type Field string
|
|
||||||
|
|
||||||
func (f Field) Name() string {
|
|
||||||
return strings.Split(string(f), ".")[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) String() string {
|
if conf.MaxConns > 0 {
|
||||||
return string(f)
|
cfg.MaxConns = conf.MaxConns // 100
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count fn wrapping of field
|
if conf.MinConns > 0 {
|
||||||
func (f Field) Count() Field {
|
cfg.MinConns = conf.MaxConns // 5
|
||||||
return Field("COUNT(" + f.String() + ")")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringEscape will return a empty string for null value
|
if conf.MaxConnLifetime > 0 {
|
||||||
func (f Field) StringEscape() Field {
|
cfg.MaxConnLifetime = conf.MaxConnLifetime // time.Minute * 10
|
||||||
return Field("COALESCE(" + f.String() + ", '')")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumberEscape will return a zero string for null value
|
if conf.MaxConnIdleTime > 0 {
|
||||||
func (f Field) NumberEscape() Field {
|
cfg.MaxConnIdleTime = conf.MaxConnIdleTime // time.Minute * 5
|
||||||
return Field("COALESCE(" + f.String() + ", 0)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BooleanEscape will return a false for null value
|
p, err := pgxpool.NewWithConfig(context.Background(), cfg)
|
||||||
func (f Field) BooleanEscape() Field {
|
if err != nil {
|
||||||
return Field("COALESCE(" + f.String() + ", FALSE)")
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avg fn wrapping of field
|
if err = p.Ping(context.Background()); err != nil {
|
||||||
func (f Field) Avg() Field {
|
panic(err)
|
||||||
return Field("AVG(" + f.String() + ")")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Sum() Field {
|
poolPGX.Store(p)
|
||||||
return Field("SUM(" + f.String() + ")")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Max() Field {
|
// GetPool instance
|
||||||
return Field("MAX(" + f.String() + ")")
|
func GetPool() *pgxpool.Pool {
|
||||||
|
return poolPGX.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Min() Field {
|
// BeginTx begins a pgx poll transaction
|
||||||
return Field("Min(" + f.String() + ")")
|
func BeginTx(ctx context.Context) (pgx.Tx, error) {
|
||||||
|
tx, err := poolPGX.Load().Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return nil, errors.New("failed to open db tx")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Lower() Field {
|
return tx, err
|
||||||
return Field("LOWER(" + f.String() + ")")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Upper() Field {
|
// IsNotFound error check
|
||||||
return Field("UPPER(" + f.String() + ")")
|
func IsNotFound(err error) bool {
|
||||||
|
return errors.Is(err, pgx.ErrNoRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Field) Trim() Field {
|
|
||||||
return Field("TRIM(" + f.String() + ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) IsNull() Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, op: " IS NULL", len: len(col) + 8}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) IsNotNull() Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, op: " IS NOT NULL", len: len(col) + 12}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eq is equal
|
|
||||||
func (f Field) Eq(val any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " = $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EqualFold will use LOWER(column_name) = LOWER(val) for comparision
|
|
||||||
func (f Field) EqFold(val string) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: "LOWER(" + col + ")", Val: val, op: " = LOWER($", action: CondActionNeedToClose, len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) NEq(val any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " != $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) Gt(val any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " > $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) Lt(val any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " < $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) Gte(val any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " >= $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) Lte(val any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " <= $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) Like(val string) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " LIKE $", len: len(f.String()) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) LikeFold(val string) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: "LOWER(" + col + ")", Val: val, op: " LIKE LOWER($", action: CondActionNeedToClose, len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ILIKE is case-insensitive
|
|
||||||
func (f Field) ILike(val string) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " ILIKE $", len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) NotIn(val ...any) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: val, op: " NOT IN($", action: CondActionNeedToClose, len: len(col) + 5}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field) NotInSubQuery(qry WhereClause) Conditioner {
|
|
||||||
col := f.String()
|
|
||||||
return &Cond{Field: col, Val: qry, op: " NOT IN($)", action: CondActionSubQuery}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Helper func ==>
|
|
||||||
//
|
|
||||||
|
|
||||||
// PgTime as in UTC
|
// PgTime as in UTC
|
||||||
func PgTime(t time.Time) pgtype.Timestamptz {
|
func PgTime(t time.Time) pgtype.Timestamptz {
|
||||||
return pgtype.Timestamptz{Time: t, Valid: true}
|
return pgtype.Timestamptz{Time: t, Valid: true}
|
||||||
@@ -173,15 +106,3 @@ func PgTime(t time.Time) pgtype.Timestamptz {
|
|||||||
func PgTimeNow() pgtype.Timestamptz {
|
func PgTimeNow() pgtype.Timestamptz {
|
||||||
return pgtype.Timestamptz{Time: time.Now(), Valid: true}
|
return pgtype.Timestamptz{Time: time.Now(), Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConcatWs(sep string, fields ...Field) Field {
|
|
||||||
return Field("concat_ws('" + sep + "'," + joinFileds(fields) + ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringAgg(exp, sep string) Field {
|
|
||||||
return Field("string_agg(" + exp + ",'" + sep + "')")
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringAggCast(exp, sep string) Field {
|
|
||||||
return Field("string_agg(cast(" + exp + " as varchar),'" + sep + "')")
|
|
||||||
}
|
|
||||||
|
|||||||
238
pgm_field.go
Normal file
238
pgm_field.go
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package pgm
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Field related to a table
|
||||||
|
type Field string
|
||||||
|
|
||||||
|
func (f Field) Name() string {
|
||||||
|
return strings.Split(string(f), ".")[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) String() string {
|
||||||
|
return string(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count function wrapped field
|
||||||
|
func (f Field) Count() Field {
|
||||||
|
return Field("COUNT(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringEscape will wrap field with:
|
||||||
|
//
|
||||||
|
// COALESCE(field, ”)
|
||||||
|
func (f Field) StringEscape() Field {
|
||||||
|
return Field("COALESCE(" + f.String() + ", '')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberEscape will wrap field with:
|
||||||
|
//
|
||||||
|
// COALESCE(field, 0)
|
||||||
|
func (f Field) NumberEscape() Field {
|
||||||
|
return Field("COALESCE(" + f.String() + ", 0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BooleanEscape will wrap field with:
|
||||||
|
//
|
||||||
|
// COALESCE(field, FALSE)
|
||||||
|
func (f Field) BooleanEscape() Field {
|
||||||
|
return Field("COALESCE(" + f.String() + ", FALSE)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avg function wrapped field
|
||||||
|
func (f Field) Avg() Field {
|
||||||
|
return Field("AVG(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum function wrapped field
|
||||||
|
func (f Field) Sum() Field {
|
||||||
|
return Field("SUM(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max function wrapped field
|
||||||
|
func (f Field) Max() Field {
|
||||||
|
return Field("MAX(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min function wrapped field
|
||||||
|
func (f Field) Min() Field {
|
||||||
|
return Field("Min(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower function wrapped field
|
||||||
|
func (f Field) Lower() Field {
|
||||||
|
return Field("LOWER(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upper function wrapped field
|
||||||
|
func (f Field) Upper() Field {
|
||||||
|
return Field("UPPER(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim function wrapped field
|
||||||
|
func (f Field) Trim() Field {
|
||||||
|
return Field("TRIM(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asc suffixed field, supposed to be used with order by
|
||||||
|
func (f Field) Asc() Field {
|
||||||
|
return Field(f.String() + " ASC")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desc suffixed field, supposed to be used with order by
|
||||||
|
func (f Field) Desc() Field {
|
||||||
|
return Field(f.String() + " DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) RowNumber(as string) Field {
|
||||||
|
return rowNumber(&f, nil, true, as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) RowNumberDesc(as string) Field {
|
||||||
|
return rowNumber(&f, nil, true, as)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RowNumberPartionBy in ascending order
|
||||||
|
func (f Field) RowNumberPartionBy(partition Field, as string) Field {
|
||||||
|
return rowNumber(&f, &partition, true, as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) RowNumberDescPartionBy(partition Field, as string) Field {
|
||||||
|
return rowNumber(&f, &partition, false, as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rowNumber(f, partition *Field, isAsc bool, as string) Field {
|
||||||
|
var orderBy string
|
||||||
|
if isAsc {
|
||||||
|
orderBy = " ASC"
|
||||||
|
} else {
|
||||||
|
orderBy = " DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
if as == "" {
|
||||||
|
as = "row_number"
|
||||||
|
}
|
||||||
|
|
||||||
|
col := f.String()
|
||||||
|
if partition != nil {
|
||||||
|
return Field("ROW_NUMBER() OVER (PARTITION BY " + partition.String() + " ORDER BY " + col + orderBy + ") AS " + as)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Field("ROW_NUMBER() OVER (ORDER BY " + col + orderBy + ") AS " + as)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) IsNull() Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, op: " IS NULL", len: len(col) + 8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) IsNotNull() Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, op: " IS NOT NULL", len: len(col) + 12}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateTrunc will truncate date or timestamp to specified level of precision
|
||||||
|
//
|
||||||
|
// Level values:
|
||||||
|
// - microseconds, milliseconds, second, minute, hour
|
||||||
|
// - day, week (Monday start), month, quarter, year
|
||||||
|
// - decade, century, millennium
|
||||||
|
func (f Field) DateTrunc(level, as string) Field {
|
||||||
|
return Field("DATE_TRUNC('" + level + "', " + f.String() + ") AS " + as)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualFold will use LOWER(column_name) = LOWER(val) for comparision
|
||||||
|
func (f Field) EqFold(val string) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: "LOWER(" + col + ")", Val: val, op: " = LOWER($", action: CondActionNeedToClose, len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eq is equal
|
||||||
|
func (f Field) Eq(val any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " = $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) NotEq(val any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " != $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Gt(val any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " > $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Lt(val any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " < $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Gte(val any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " >= $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Lte(val any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " <= $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Like(val string) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " LIKE $", len: len(f.String()) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) LikeFold(val string) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: "LOWER(" + col + ")", Val: val, op: " LIKE LOWER($", action: CondActionNeedToClose, len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ILIKE is case-insensitive
|
||||||
|
func (f Field) ILike(val string) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " ILIKE $", len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Any(val ...any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " = ANY($", action: CondActionNeedToClose, len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) NotAny(val ...any) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: val, op: " != ANY($", action: CondActionNeedToClose, len: len(col) + 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotInSubQuery using ANY
|
||||||
|
func (f Field) NotInSubQuery(qry WhereClause) Conditioner {
|
||||||
|
col := f.String()
|
||||||
|
return &Cond{Field: col, Val: qry, op: " != ANY($)", action: CondActionSubQuery}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConcatWs(sep string, fields ...Field) Field {
|
||||||
|
return Field("concat_ws('" + sep + "'," + joinFileds(fields) + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringAgg(exp, sep string) Field {
|
||||||
|
return Field("string_agg(" + exp + ",'" + sep + "')")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringAggCast(exp, sep string) Field {
|
||||||
|
return Field("string_agg(cast(" + exp + " as varchar),'" + sep + "')")
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinFileds(fields []Field) string {
|
||||||
|
sb := getSB()
|
||||||
|
defer putSB(sb)
|
||||||
|
for i, f := range fields {
|
||||||
|
if i == 0 {
|
||||||
|
sb.WriteString(f.String())
|
||||||
|
} else {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
sb.WriteString(f.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
71
pgm_table.go
Normal file
71
pgm_table.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package pgm
|
||||||
|
|
||||||
|
// Table in database
|
||||||
|
type Table struct {
|
||||||
|
Name string
|
||||||
|
DerivedTable Query
|
||||||
|
PK []string
|
||||||
|
FieldCount uint16
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug when set true will print generated query string in stdout
|
||||||
|
func (t *Table) Debug() Clause {
|
||||||
|
t.debug = true
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) Field(f string) Field {
|
||||||
|
return Field(t.Name + "." + f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert table statement
|
||||||
|
func (t *Table) Insert() InsertClause {
|
||||||
|
qb := &insertQry{
|
||||||
|
debug: t.debug,
|
||||||
|
table: t.Name,
|
||||||
|
fields: make([]string, 0, t.FieldCount),
|
||||||
|
vals: make([]string, 0, t.FieldCount),
|
||||||
|
args: make([]any, 0, t.FieldCount),
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select table statement
|
||||||
|
func (t *Table) Select(field ...Field) SelectClause {
|
||||||
|
qb := &selectQry{
|
||||||
|
debug: t.debug,
|
||||||
|
fields: field,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.DerivedTable != nil {
|
||||||
|
tName, args := t.DerivedTable.Build(true)
|
||||||
|
qb.table = "(" + tName + ") AS " + t.Name
|
||||||
|
qb.args = args
|
||||||
|
} else {
|
||||||
|
qb.table = t.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update table statement
|
||||||
|
func (t *Table) Update() UpdateClause {
|
||||||
|
qb := &updateQry{
|
||||||
|
debug: t.debug,
|
||||||
|
table: t.Name,
|
||||||
|
cols: make([]string, 0, t.FieldCount),
|
||||||
|
args: make([]any, 0, t.FieldCount),
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detlete table statement
|
||||||
|
func (t *Table) Delete() DeleteCluase {
|
||||||
|
qb := &deleteQry{
|
||||||
|
debug: t.debug,
|
||||||
|
table: t.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return qb
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
// Code generated by db-gen. DO NOT EDIT.
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
|
|
||||||
package branchuser
|
package branchuser
|
||||||
|
|
||||||
import "code.patial.tech/go/pgm"
|
import "code.patial.tech/go/pgm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// All fields in table branch_users
|
||||||
|
All pgm.Field = "branch_users.*"
|
||||||
// BranchID field has db type "bigint NOT NULL"
|
// BranchID field has db type "bigint NOT NULL"
|
||||||
BranchID pgm.Field = "branch_users.branch_id"
|
BranchID pgm.Field = "branch_users.branch_id"
|
||||||
// UserID field has db type "bigint NOT NULL"
|
// UserID field has db type "bigint NOT NULL"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// Code generated by db-gen. DO NOT EDIT.
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
|
|
||||||
package comment
|
package comment
|
||||||
|
|
||||||
import "code.patial.tech/go/pgm"
|
import "code.patial.tech/go/pgm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// All fields in table comments
|
||||||
|
All pgm.Field = "comments.*"
|
||||||
// ID field has db type "integer NOT NULL"
|
// ID field has db type "integer NOT NULL"
|
||||||
ID pgm.Field = "comments.id"
|
ID pgm.Field = "comments.id"
|
||||||
// PostID field has db type "integer NOT NULL"
|
// PostID field has db type "integer NOT NULL"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// Code generated by db-gen. DO NOT EDIT.
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
|
|
||||||
package employee
|
package employee
|
||||||
|
|
||||||
import "code.patial.tech/go/pgm"
|
import "code.patial.tech/go/pgm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// All fields in table employees
|
||||||
|
All pgm.Field = "employees.*"
|
||||||
// ID field has db type "integer NOT NULL"
|
// ID field has db type "integer NOT NULL"
|
||||||
ID pgm.Field = "employees.id"
|
ID pgm.Field = "employees.id"
|
||||||
// Name field has db type "var NOT NULL"
|
// Name field has db type "var NOT NULL"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// Code generated by db-gen. DO NOT EDIT.
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
|
|
||||||
package post
|
package post
|
||||||
|
|
||||||
import "code.patial.tech/go/pgm"
|
import "code.patial.tech/go/pgm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// All fields in table posts
|
||||||
|
All pgm.Field = "posts.*"
|
||||||
// ID field has db type "integer NOT NULL"
|
// ID field has db type "integer NOT NULL"
|
||||||
ID pgm.Field = "posts.id"
|
ID pgm.Field = "posts.id"
|
||||||
// UserID field has db type "integer NOT NULL"
|
// UserID field has db type "integer NOT NULL"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// Code generated by code.patial.tech/go/pgm/cmd
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
// DO NOT EDIT.
|
|
||||||
|
|
||||||
package db
|
package db
|
||||||
|
|
||||||
@@ -13,3 +12,11 @@ var (
|
|||||||
Comment = pgm.Table{Name: "comments", FieldCount: 5}
|
Comment = pgm.Table{Name: "comments", FieldCount: 5}
|
||||||
Employee = pgm.Table{Name: "employees", FieldCount: 5}
|
Employee = pgm.Table{Name: "employees", FieldCount: 5}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func DerivedTable(tblName string, fromQry pgm.Query) pgm.Table {
|
||||||
|
t := pgm.Table{
|
||||||
|
Name: tblName,
|
||||||
|
DerivedTable: fromQry,
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// Code generated by db-gen. DO NOT EDIT.
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
|
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import "code.patial.tech/go/pgm"
|
import "code.patial.tech/go/pgm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// All fields in table users
|
||||||
|
All pgm.Field = "users.*"
|
||||||
// ID field has db type "integer NOT NULL"
|
// ID field has db type "integer NOT NULL"
|
||||||
ID pgm.Field = "users.id"
|
ID pgm.Field = "users.id"
|
||||||
// Name field has db type "character varying(255) NOT NULL"
|
// Name field has db type "character varying(255) NOT NULL"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// Code generated by db-gen. DO NOT EDIT.
|
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
|
||||||
|
|
||||||
package usersession
|
package usersession
|
||||||
|
|
||||||
import "code.patial.tech/go/pgm"
|
import "code.patial.tech/go/pgm"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// All fields in table user_sessions
|
||||||
|
All pgm.Field = "user_sessions.*"
|
||||||
// ID field has db type "character varying NOT NULL"
|
// ID field has db type "character varying NOT NULL"
|
||||||
ID pgm.Field = "user_sessions.id"
|
ID pgm.Field = "user_sessions.id"
|
||||||
// CreatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"
|
// CreatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
expected := "DELETE FROM users WHERE users.id = $1 AND users.status_id NOT IN($2)"
|
expected := "DELETE FROM users WHERE users.id = $1 AND users.status_id != ANY($2)"
|
||||||
got := db.User.Delete().
|
got := db.User.Delete().
|
||||||
Where(user.ID.Eq(1), user.StatusID.NotIn(1, 2, 3)).
|
Where(user.ID.Eq(1), user.StatusID.NotAny(1, 2, 3)).
|
||||||
String()
|
String()
|
||||||
if got != expected {
|
if got != expected {
|
||||||
t.Errorf("got %q, want %q", got, expected)
|
t.Errorf("got %q, want %q", got, expected)
|
||||||
|
|||||||
@@ -23,23 +23,6 @@ func TestInsertQuery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsertSetMap(t *testing.T) {
|
|
||||||
got := db.User.Insert().
|
|
||||||
SetMap(map[pgm.Field]any{
|
|
||||||
user.Email: "aa@aa.com",
|
|
||||||
user.Phone: 8889991234,
|
|
||||||
user.FirstName: "fname",
|
|
||||||
user.LastName: "lname",
|
|
||||||
}).
|
|
||||||
Returning(user.ID).
|
|
||||||
String()
|
|
||||||
|
|
||||||
expected := "INSERT INTO users(email, phone, first_name, last_name) VALUES($1, $2, $3, $4) RETURNING id"
|
|
||||||
if got != expected {
|
|
||||||
t.Errorf("\nexpected: %q\ngot: %q", expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsertQuery2(t *testing.T) {
|
func TestInsertQuery2(t *testing.T) {
|
||||||
got := db.User.Insert().
|
got := db.User.Insert().
|
||||||
Set(user.Email, "aa@aa.com").
|
Set(user.Email, "aa@aa.com").
|
||||||
@@ -53,7 +36,7 @@ func TestInsertQuery2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BenchmarkInsertQuery-12 1952412 605.3 ns/op 1144 B/op 18 allocs/op
|
// BenchmarkInsertQuery-12 2014519 584.0 ns/op 1144 B/op 18 allocs/op
|
||||||
func BenchmarkInsertQuery(b *testing.B) {
|
func BenchmarkInsertQuery(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
_ = db.User.Insert().
|
_ = db.User.Insert().
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func TestQryBuilder2(t *testing.T) {
|
|||||||
),
|
),
|
||||||
).
|
).
|
||||||
Where(
|
Where(
|
||||||
user.LastName.NEq(7),
|
user.LastName.NotEq(7),
|
||||||
user.Phone.Like("%123%"),
|
user.Phone.Like("%123%"),
|
||||||
user.UpdatedAt.IsNotNull(),
|
user.UpdatedAt.IsNotNull(),
|
||||||
user.Email.NotInSubQuery(db.User.Select(user.ID).Where(user.ID.Eq(123))),
|
user.Email.NotInSubQuery(db.User.Select(user.ID).Where(user.ID.Eq(123))),
|
||||||
@@ -40,7 +40,7 @@ func TestQryBuilder2(t *testing.T) {
|
|||||||
expected := "SELECT users.email, users.first_name FROM users JOIN user_sessions ON users.id = user_sessions.user_id" +
|
expected := "SELECT users.email, users.first_name FROM users JOIN user_sessions ON users.id = user_sessions.user_id" +
|
||||||
" JOIN branch_users ON users.id = branch_users.user_id WHERE users.id = $1 AND (users.status_id = $2 OR users.updated_at = $3)" +
|
" JOIN branch_users ON users.id = branch_users.user_id WHERE users.id = $1 AND (users.status_id = $2 OR users.updated_at = $3)" +
|
||||||
" AND users.mfa_kind = $4 AND (users.first_name = $5 OR users.middle_name = $6) AND users.last_name != $7 AND users.phone" +
|
" AND users.mfa_kind = $4 AND (users.first_name = $5 OR users.middle_name = $6) AND users.last_name != $7 AND users.phone" +
|
||||||
" LIKE $8 AND users.updated_at IS NOT NULL AND users.email NOT IN(SELECT users.id FROM users WHERE users.id = $9) LIMIT 10 OFFSET 100"
|
" LIKE $8 AND users.updated_at IS NOT NULL AND users.email != ANY(SELECT users.id FROM users WHERE users.id = $9) LIMIT 10 OFFSET 100"
|
||||||
if expected != got {
|
if expected != got {
|
||||||
t.Errorf("\nexpected: %q\ngot: %q", expected, got)
|
t.Errorf("\nexpected: %q\ngot: %q", expected, got)
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,52 @@ func TestSelectWithHaving(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BenchmarkSelect-12 668817 1753 ns/op 4442 B/op 59 allocs/op
|
func TestSelectDerived(t *testing.T) {
|
||||||
|
expected := "SELECT t.* FROM (SELECT users.*, ROW_NUMBER() OVER (PARTITION BY users.status_id ORDER BY users.created_at DESC) AS rn" +
|
||||||
|
" FROM users WHERE users.status_id = $1) AS t WHERE t.rn <= $2" +
|
||||||
|
" ORDER BY t.status_id, t.created_at DESC"
|
||||||
|
|
||||||
|
qry := db.User.
|
||||||
|
Select(user.All, user.CreatedAt.RowNumberDescPartionBy(user.StatusID, "rn")).
|
||||||
|
Where(user.StatusID.Eq(1))
|
||||||
|
|
||||||
|
tbl := db.DerivedTable("t", qry)
|
||||||
|
got := tbl.
|
||||||
|
Select(tbl.Field("*")).
|
||||||
|
Where(tbl.Field("rn").Lte(5)).
|
||||||
|
OrderBy(tbl.Field("status_id"), tbl.Field("created_at").Desc()).
|
||||||
|
String()
|
||||||
|
|
||||||
|
if expected != got {
|
||||||
|
t.Errorf("\nexpected: %q\n\ngot: %q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectWithJoin(t *testing.T) {
|
||||||
|
got := db.User.Select(user.Email, user.FirstName).
|
||||||
|
Join(db.UserSession, user.ID, usersession.UserID).
|
||||||
|
LeftJoin(db.BranchUser, user.ID, branchuser.UserID, pgm.Or(branchuser.RoleID.Eq("1"), branchuser.RoleID.Eq("2"))).
|
||||||
|
Where(
|
||||||
|
user.ID.Eq(3),
|
||||||
|
pgm.Or(
|
||||||
|
user.StatusID.Eq(4),
|
||||||
|
user.UpdatedAt.Eq(5),
|
||||||
|
),
|
||||||
|
).
|
||||||
|
Limit(10).
|
||||||
|
Offset(100).
|
||||||
|
String()
|
||||||
|
|
||||||
|
expected := "SELECT users.email, users.first_name " +
|
||||||
|
"FROM users JOIN user_sessions ON users.id = user_sessions.user_id " +
|
||||||
|
"LEFT JOIN branch_users ON users.id = branch_users.user_id AND (branch_users.role_id = $1 OR branch_users.role_id = $2) " +
|
||||||
|
"WHERE users.id = $3 AND (users.status_id = $4 OR users.updated_at = $5) " +
|
||||||
|
"LIMIT 10 OFFSET 100"
|
||||||
|
if expected != got {
|
||||||
|
t.Errorf("\nexpected: %q\ngot: %q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BenchmarkSelect-12 638901 1860 ns/op 4266 B/op 61 allocs/op
|
// BenchmarkSelect-12 638901 1860 ns/op 4266 B/op 61 allocs/op
|
||||||
func BenchmarkSelect(b *testing.B) {
|
func BenchmarkSelect(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
@@ -80,7 +125,7 @@ func BenchmarkSelect(b *testing.B) {
|
|||||||
),
|
),
|
||||||
).
|
).
|
||||||
Where(
|
Where(
|
||||||
user.LastName.NEq(7),
|
user.LastName.NotEq(7),
|
||||||
user.Phone.Like("%123%"),
|
user.Phone.Like("%123%"),
|
||||||
user.Email.NotInSubQuery(db.User.Select(user.ID).Where(user.ID.Eq(123))),
|
user.Email.NotInSubQuery(db.User.Select(user.ID).Where(user.ID.Eq(123))),
|
||||||
).
|
).
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package playground
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.patial.tech/go/pgm"
|
|
||||||
"code.patial.tech/go/pgm/playground/db"
|
"code.patial.tech/go/pgm/playground/db"
|
||||||
"code.patial.tech/go/pgm/playground/db/user"
|
"code.patial.tech/go/pgm/playground/db/user"
|
||||||
)
|
)
|
||||||
@@ -17,28 +16,7 @@ func TestUpdateQuery(t *testing.T) {
|
|||||||
user.Email.Eq("aa@aa.com"),
|
user.Email.Eq("aa@aa.com"),
|
||||||
).
|
).
|
||||||
Where(
|
Where(
|
||||||
user.StatusID.NEq(1),
|
user.StatusID.NotEq(1),
|
||||||
).
|
|
||||||
String()
|
|
||||||
|
|
||||||
expected := "UPDATE users SET first_name=$1, middle_name=$2, last_name=$3 WHERE users.email = $4 AND users.status_id != $5"
|
|
||||||
if got != expected {
|
|
||||||
t.Errorf("\nexpected: %q\ngot: %q", expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateSetMap(t *testing.T) {
|
|
||||||
got := db.User.Update().
|
|
||||||
SetMap(map[pgm.Field]any{
|
|
||||||
user.FirstName: "ankit",
|
|
||||||
user.MiddleName: "singh",
|
|
||||||
user.LastName: "patial",
|
|
||||||
}).
|
|
||||||
Where(
|
|
||||||
user.Email.Eq("aa@aa.com"),
|
|
||||||
).
|
|
||||||
Where(
|
|
||||||
user.StatusID.NEq(1),
|
|
||||||
).
|
).
|
||||||
String()
|
String()
|
||||||
|
|
||||||
@@ -59,7 +37,7 @@ func BenchmarkUpdateQuery(b *testing.B) {
|
|||||||
user.Email.Eq("aa@aa.com"),
|
user.Email.Eq("aa@aa.com"),
|
||||||
).
|
).
|
||||||
Where(
|
Where(
|
||||||
user.StatusID.NEq(1),
|
user.StatusID.NotEq(1),
|
||||||
).
|
).
|
||||||
String()
|
String()
|
||||||
}
|
}
|
||||||
|
|||||||
110
pool.go
110
pool.go
@@ -1,110 +0,0 @@
|
|||||||
// Patial Tech.
|
|
||||||
// Author, Ankit Patial
|
|
||||||
|
|
||||||
package pgm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log/slog"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
poolPGX atomic.Pointer[pgxpool.Pool]
|
|
||||||
poolStringBuilder = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return new(strings.Builder)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrConnStringMissing = errors.New("connection string is empty")
|
|
||||||
ErrInitTX = errors.New("failed to init db.tx")
|
|
||||||
ErrCommitTX = errors.New("failed to commit db.tx")
|
|
||||||
ErrNoRows = errors.New("no data found")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
ConnString string
|
|
||||||
MaxConns int32
|
|
||||||
MinConns int32
|
|
||||||
MaxConnLifetime time.Duration
|
|
||||||
MaxConnIdleTime time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitPool will create new pgxpool.Pool and will keep it for its working
|
|
||||||
func InitPool(conf Config) {
|
|
||||||
if conf.ConnString == "" {
|
|
||||||
panic(ErrConnStringMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := pgxpool.ParseConfig(conf.ConnString)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.MaxConns > 0 {
|
|
||||||
cfg.MaxConns = conf.MaxConns // 100
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.MinConns > 0 {
|
|
||||||
cfg.MinConns = conf.MaxConns // 5
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.MaxConnLifetime > 0 {
|
|
||||||
cfg.MaxConnLifetime = conf.MaxConnLifetime // time.Minute * 10
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.MaxConnIdleTime > 0 {
|
|
||||||
cfg.MaxConnIdleTime = conf.MaxConnIdleTime // time.Minute * 5
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := pgxpool.NewWithConfig(context.Background(), cfg)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = p.Ping(context.Background()); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
poolPGX.Store(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get string builder from pool
|
|
||||||
func getSB() *strings.Builder {
|
|
||||||
return poolStringBuilder.Get().(*strings.Builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// put string builder back to pool
|
|
||||||
func putSB(sb *strings.Builder) {
|
|
||||||
sb.Reset()
|
|
||||||
poolStringBuilder.Put(sb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPool instance
|
|
||||||
func GetPool() *pgxpool.Pool {
|
|
||||||
return poolPGX.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeginTx begins a pgx poll transaction
|
|
||||||
func BeginTx(ctx context.Context) (pgx.Tx, error) {
|
|
||||||
tx, err := poolPGX.Load().Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error(err.Error())
|
|
||||||
return nil, errors.New("failed to open db tx")
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotFound error check
|
|
||||||
func IsNotFound(err error) bool {
|
|
||||||
return errors.Is(err, pgx.ErrNoRows)
|
|
||||||
}
|
|
||||||
177
qry.go
177
qry.go
@@ -7,156 +7,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Clause interface {
|
Clause interface {
|
||||||
|
Insert() InsertClause
|
||||||
Select(fields ...Field) SelectClause
|
Select(fields ...Field) SelectClause
|
||||||
// Insert() InsertSet
|
Update() UpdateClause
|
||||||
// Update() UpdateSet
|
Delete() DeleteCluase
|
||||||
// Delete() WhereOrExec
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectClause interface {
|
|
||||||
// Join and Inner Join are same
|
|
||||||
Join(m Table, t1Field, t2Field Field) SelectClause
|
|
||||||
LeftJoin(m Table, t1Field, t2Field Field) SelectClause
|
|
||||||
RightJoin(m Table, t1Field, t2Field Field) SelectClause
|
|
||||||
FullJoin(m Table, t1Field, t2Field Field) SelectClause
|
|
||||||
CrossJoin(m Table) SelectClause
|
|
||||||
WhereClause
|
|
||||||
OrderByClause
|
|
||||||
GroupByClause
|
|
||||||
LimitClause
|
|
||||||
OffsetClause
|
|
||||||
Query
|
|
||||||
raw(prefixArgs []any) (string, []any)
|
|
||||||
}
|
|
||||||
|
|
||||||
WhereClause interface {
|
|
||||||
Where(cond ...Conditioner) AfterWhere
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterWhere interface {
|
|
||||||
WhereClause
|
|
||||||
GroupByClause
|
|
||||||
OrderByClause
|
|
||||||
LimitClause
|
|
||||||
OffsetClause
|
|
||||||
Query
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupByClause interface {
|
|
||||||
GroupBy(fields ...Field) AfterGroupBy
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterGroupBy interface {
|
|
||||||
HavinClause
|
|
||||||
OrderByClause
|
|
||||||
LimitClause
|
|
||||||
OffsetClause
|
|
||||||
Query
|
|
||||||
}
|
|
||||||
|
|
||||||
HavinClause interface {
|
|
||||||
Having(cond ...Conditioner) AfterHaving
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterHaving interface {
|
|
||||||
OrderByClause
|
|
||||||
LimitClause
|
|
||||||
OffsetClause
|
|
||||||
Query
|
|
||||||
}
|
|
||||||
|
|
||||||
OrderByClause interface {
|
|
||||||
OrderBy(fields ...Field) AfterOrderBy
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterOrderBy interface {
|
|
||||||
LimitClause
|
|
||||||
OffsetClause
|
|
||||||
Query
|
|
||||||
}
|
|
||||||
|
|
||||||
LimitClause interface {
|
|
||||||
Limit(v int) AfterLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterLimit interface {
|
|
||||||
OffsetClause
|
|
||||||
Query
|
|
||||||
}
|
|
||||||
|
|
||||||
OffsetClause interface {
|
|
||||||
Offset(v int) AfterOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
AfterOffset interface {
|
|
||||||
LimitClause
|
|
||||||
Query
|
|
||||||
}
|
|
||||||
|
|
||||||
Conditioner interface {
|
|
||||||
Condition(args *[]any, idx int) string
|
|
||||||
}
|
|
||||||
|
|
||||||
Insert interface {
|
|
||||||
Set(field Field, val any) InsertClause
|
|
||||||
SetMap(fields map[Field]any) InsertClause
|
|
||||||
}
|
|
||||||
|
|
||||||
InsertClause interface {
|
|
||||||
Insert
|
|
||||||
Returning(field Field) First
|
|
||||||
OnConflict(fields ...Field) Do
|
|
||||||
Execute
|
|
||||||
Stringer
|
|
||||||
}
|
|
||||||
|
|
||||||
Do interface {
|
|
||||||
DoNothing() Execute
|
|
||||||
DoUpdate(fields ...Field) Execute
|
|
||||||
}
|
|
||||||
|
|
||||||
Update interface {
|
|
||||||
Set(field Field, val any) UpdateClause
|
|
||||||
SetMap(fields map[Field]any) UpdateClause
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateClause interface {
|
|
||||||
Update
|
|
||||||
Where(cond ...Conditioner) WhereOrExec
|
|
||||||
}
|
|
||||||
|
|
||||||
WhereOrExec interface {
|
|
||||||
Where(cond ...Conditioner) WhereOrExec
|
|
||||||
Execute
|
|
||||||
}
|
|
||||||
|
|
||||||
Query interface {
|
|
||||||
First
|
|
||||||
All
|
|
||||||
Stringer
|
|
||||||
}
|
|
||||||
|
|
||||||
First interface {
|
|
||||||
First(ctx context.Context, dest ...any) error
|
|
||||||
FirstTx(ctx context.Context, tx pgx.Tx, dest ...any) error
|
|
||||||
Stringer
|
|
||||||
}
|
|
||||||
|
|
||||||
All interface {
|
|
||||||
// Query rows
|
|
||||||
//
|
|
||||||
// don't forget to close() rows
|
|
||||||
All(ctx context.Context, rows RowsCb) error
|
|
||||||
// Query rows
|
|
||||||
//
|
|
||||||
// don't forget to close() rows
|
|
||||||
AllTx(ctx context.Context, tx pgx.Tx, rows RowsCb) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Execute interface {
|
Execute interface {
|
||||||
@@ -169,26 +30,26 @@ type (
|
|||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
RowScanner interface {
|
Conditioner interface {
|
||||||
Scan(dest ...any) error
|
Condition(args *[]any, idx int) string
|
||||||
}
|
}
|
||||||
|
|
||||||
RowsCb func(row RowScanner) error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func joinFileds(fields []Field) string {
|
var sbPool = sync.Pool{
|
||||||
sb := getSB()
|
New: func() any {
|
||||||
defer putSB(sb)
|
return new(strings.Builder)
|
||||||
for i, f := range fields {
|
},
|
||||||
if i == 0 {
|
|
||||||
sb.WriteString(f.String())
|
|
||||||
} else {
|
|
||||||
sb.WriteString(", ")
|
|
||||||
sb.WriteString(f.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
// get string builder from pool
|
||||||
|
func getSB() *strings.Builder {
|
||||||
|
return sbPool.Get().(*strings.Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put string builder back to pool
|
||||||
|
func putSB(sb *strings.Builder) {
|
||||||
|
sb.Reset()
|
||||||
|
sbPool.Put(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func And(cond ...Conditioner) Conditioner {
|
func And(cond ...Conditioner) Conditioner {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
DeleteCluase interface {
|
||||||
|
WhereOrExec
|
||||||
|
}
|
||||||
|
|
||||||
deleteQry struct {
|
deleteQry struct {
|
||||||
table string
|
table string
|
||||||
condition []Conditioner
|
condition []Conditioner
|
||||||
@@ -15,15 +19,6 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Table) Delete() WhereOrExec {
|
|
||||||
qb := &deleteQry{
|
|
||||||
table: t.Name,
|
|
||||||
debug: t.debug,
|
|
||||||
}
|
|
||||||
|
|
||||||
return qb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *deleteQry) Where(cond ...Conditioner) WhereOrExec {
|
func (q *deleteQry) Where(cond ...Conditioner) WhereOrExec {
|
||||||
q.condition = append(q.condition, cond...)
|
q.condition = append(q.condition, cond...)
|
||||||
return q
|
return q
|
||||||
|
|||||||
@@ -12,7 +12,21 @@ import (
|
|||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type insertQry struct {
|
type (
|
||||||
|
InsertClause interface {
|
||||||
|
Insert
|
||||||
|
Returning(field Field) First
|
||||||
|
OnConflict(fields ...Field) Do
|
||||||
|
Execute
|
||||||
|
Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
Insert interface {
|
||||||
|
Set(field Field, val any) InsertClause
|
||||||
|
SetMap(fields map[Field]any) InsertClause
|
||||||
|
}
|
||||||
|
|
||||||
|
insertQry struct {
|
||||||
returing *string
|
returing *string
|
||||||
onConflict *string
|
onConflict *string
|
||||||
|
|
||||||
@@ -24,17 +38,7 @@ type insertQry struct {
|
|||||||
args []any
|
args []any
|
||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
)
|
||||||
func (t *Table) Insert() Insert {
|
|
||||||
qb := &insertQry{
|
|
||||||
table: t.Name,
|
|
||||||
fields: make([]string, 0, t.FieldCount),
|
|
||||||
vals: make([]string, 0, t.FieldCount),
|
|
||||||
args: make([]any, 0, t.FieldCount),
|
|
||||||
debug: t.debug,
|
|
||||||
}
|
|
||||||
return qb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *insertQry) Set(field Field, val any) InsertClause {
|
func (q *insertQry) Set(field Field, val any) InsertClause {
|
||||||
q.fields = append(q.fields, field.Name())
|
q.fields = append(q.fields, field.Name())
|
||||||
|
|||||||
192
qry_select.go
192
qry_select.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -14,6 +15,125 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
SelectClause interface {
|
||||||
|
// Join and Inner Join are same
|
||||||
|
Join(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause
|
||||||
|
LeftJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause
|
||||||
|
RightJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause
|
||||||
|
FullJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause
|
||||||
|
CrossJoin(m Table) SelectClause
|
||||||
|
WhereClause
|
||||||
|
OrderByClause
|
||||||
|
GroupByClause
|
||||||
|
LimitClause
|
||||||
|
OffsetClause
|
||||||
|
Query
|
||||||
|
raw(prefixArgs []any) (string, []any)
|
||||||
|
}
|
||||||
|
|
||||||
|
WhereClause interface {
|
||||||
|
Where(cond ...Conditioner) AfterWhere
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterWhere interface {
|
||||||
|
WhereClause
|
||||||
|
GroupByClause
|
||||||
|
OrderByClause
|
||||||
|
LimitClause
|
||||||
|
OffsetClause
|
||||||
|
Query
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupByClause interface {
|
||||||
|
GroupBy(fields ...Field) AfterGroupBy
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterGroupBy interface {
|
||||||
|
HavinClause
|
||||||
|
OrderByClause
|
||||||
|
LimitClause
|
||||||
|
OffsetClause
|
||||||
|
Query
|
||||||
|
}
|
||||||
|
|
||||||
|
HavinClause interface {
|
||||||
|
Having(cond ...Conditioner) AfterHaving
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterHaving interface {
|
||||||
|
OrderByClause
|
||||||
|
LimitClause
|
||||||
|
OffsetClause
|
||||||
|
Query
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderByClause interface {
|
||||||
|
OrderBy(fields ...Field) AfterOrderBy
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterOrderBy interface {
|
||||||
|
LimitClause
|
||||||
|
OffsetClause
|
||||||
|
Query
|
||||||
|
}
|
||||||
|
|
||||||
|
LimitClause interface {
|
||||||
|
Limit(v int) AfterLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterLimit interface {
|
||||||
|
OffsetClause
|
||||||
|
Query
|
||||||
|
}
|
||||||
|
|
||||||
|
OffsetClause interface {
|
||||||
|
Offset(v int) AfterOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterOffset interface {
|
||||||
|
LimitClause
|
||||||
|
Query
|
||||||
|
}
|
||||||
|
|
||||||
|
Do interface {
|
||||||
|
DoNothing() Execute
|
||||||
|
DoUpdate(fields ...Field) Execute
|
||||||
|
}
|
||||||
|
|
||||||
|
Query interface {
|
||||||
|
First
|
||||||
|
All
|
||||||
|
Stringer
|
||||||
|
Bulder
|
||||||
|
}
|
||||||
|
|
||||||
|
RowScanner interface {
|
||||||
|
Scan(dest ...any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
RowsCb func(row RowScanner) error
|
||||||
|
|
||||||
|
First interface {
|
||||||
|
First(ctx context.Context, dest ...any) error
|
||||||
|
FirstTx(ctx context.Context, tx pgx.Tx, dest ...any) error
|
||||||
|
Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
All interface {
|
||||||
|
// Query rows
|
||||||
|
//
|
||||||
|
// don't forget to close() rows
|
||||||
|
All(ctx context.Context, rows RowsCb) error
|
||||||
|
// Query rows
|
||||||
|
//
|
||||||
|
// don't forget to close() rows
|
||||||
|
AllTx(ctx context.Context, tx pgx.Tx, rows RowsCb) error
|
||||||
|
}
|
||||||
|
|
||||||
|
Bulder interface {
|
||||||
|
Build(needArgs bool) (qry string, args []any)
|
||||||
|
}
|
||||||
|
|
||||||
selectQry struct {
|
selectQry struct {
|
||||||
table string
|
table string
|
||||||
fields []Field
|
fields []Field
|
||||||
@@ -23,8 +143,10 @@ type (
|
|||||||
groupBy []Field
|
groupBy []Field
|
||||||
having []Conditioner
|
having []Conditioner
|
||||||
orderBy []Field
|
orderBy []Field
|
||||||
|
|
||||||
limit int
|
limit int
|
||||||
offset int
|
offset int
|
||||||
|
|
||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,34 +173,46 @@ const (
|
|||||||
CondActionSubQuery
|
CondActionSubQuery
|
||||||
)
|
)
|
||||||
|
|
||||||
// Select clause
|
func (q *selectQry) Join(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
|
||||||
func (t Table) Select(field ...Field) SelectClause {
|
return q.buildJoin(t, "JOIN", t1Field, t2Field, cond...)
|
||||||
qb := &selectQry{
|
|
||||||
table: t.Name,
|
|
||||||
debug: t.debug,
|
|
||||||
fields: field,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return qb
|
func (q *selectQry) LeftJoin(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
|
||||||
|
return q.buildJoin(t, "LEFT JOIN", t1Field, t2Field, cond...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *selectQry) Join(t Table, t1Field, t2Field Field) SelectClause {
|
func (q *selectQry) RightJoin(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
|
||||||
q.join = append(q.join, "JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
|
return q.buildJoin(t, "RIGHT JOIN", t1Field, t2Field, cond...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *selectQry) FullJoin(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
|
||||||
|
return q.buildJoin(t, "FULL JOIN", t1Field, t2Field, cond...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *selectQry) buildJoin(t Table, joinKW string, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
|
||||||
|
str := joinKW + " " + t.Name + " ON " + t1Field.String() + " = " + t2Field.String()
|
||||||
|
if len(cond) == 0 { // Join with no condition
|
||||||
|
q.join = append(q.join, str)
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *selectQry) LeftJoin(t Table, t1Field, t2Field Field) SelectClause {
|
// Join has condition(s)
|
||||||
q.join = append(q.join, "LEFT JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
|
sb := getSB()
|
||||||
return q
|
defer putSB(sb)
|
||||||
|
sb.Grow(len(str) * 2)
|
||||||
|
|
||||||
|
sb.WriteString(str + " AND ")
|
||||||
|
|
||||||
|
var argIdx int
|
||||||
|
for i, c := range cond {
|
||||||
|
argIdx = len(q.args)
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(" AND ")
|
||||||
|
}
|
||||||
|
sb.WriteString(c.Condition(&q.args, argIdx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *selectQry) RightJoin(t Table, t1Field, t2Field Field) SelectClause {
|
q.join = append(q.join, sb.String())
|
||||||
q.join = append(q.join, "RIGHT JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *selectQry) FullJoin(t Table, t1Field, t2Field Field) SelectClause {
|
|
||||||
q.join = append(q.join, "FULL JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
|
|
||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +297,17 @@ func (q *selectQry) raw(prefixArgs []any) (string, []any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *selectQry) String() string {
|
func (q *selectQry) String() string {
|
||||||
|
qry, _ := q.Build(false)
|
||||||
|
if q.debug {
|
||||||
|
fmt.Println("***")
|
||||||
|
fmt.Println(qry)
|
||||||
|
fmt.Printf("%+v\n", q.args)
|
||||||
|
fmt.Println("***")
|
||||||
|
}
|
||||||
|
return qry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *selectQry) Build(needArgs bool) (qry string, args []any) {
|
||||||
sb := getSB()
|
sb := getSB()
|
||||||
defer putSB(sb)
|
defer putSB(sb)
|
||||||
|
|
||||||
@@ -228,14 +373,19 @@ func (q *selectQry) String() string {
|
|||||||
sb.WriteString(strconv.Itoa(q.offset))
|
sb.WriteString(strconv.Itoa(q.offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
qry := sb.String()
|
qry = sb.String()
|
||||||
if q.debug {
|
if q.debug {
|
||||||
fmt.Println("***")
|
fmt.Println("***")
|
||||||
fmt.Println(qry)
|
fmt.Println(qry)
|
||||||
fmt.Printf("%+v\n", q.args)
|
fmt.Printf("%+v\n", q.args)
|
||||||
fmt.Println("***")
|
fmt.Println("***")
|
||||||
}
|
}
|
||||||
return qry
|
|
||||||
|
if needArgs {
|
||||||
|
args = slices.Clone(q.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *selectQry) averageLen() int {
|
func (q *selectQry) averageLen() int {
|
||||||
|
|||||||
@@ -11,23 +11,30 @@ import (
|
|||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type updateQry struct {
|
type (
|
||||||
|
Update interface {
|
||||||
|
Set(field Field, val any) UpdateClause
|
||||||
|
SetMap(fields map[Field]any) UpdateClause
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateClause interface {
|
||||||
|
Update
|
||||||
|
Where(cond ...Conditioner) WhereOrExec
|
||||||
|
}
|
||||||
|
|
||||||
|
WhereOrExec interface {
|
||||||
|
Where(cond ...Conditioner) WhereOrExec
|
||||||
|
Execute
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQry struct {
|
||||||
table string
|
table string
|
||||||
cols []string
|
cols []string
|
||||||
condition []Conditioner
|
condition []Conditioner
|
||||||
args []any
|
args []any
|
||||||
debug bool
|
debug bool
|
||||||
}
|
}
|
||||||
|
)
|
||||||
func (t *Table) Update() Update {
|
|
||||||
qb := &updateQry{
|
|
||||||
table: t.Name,
|
|
||||||
debug: t.debug,
|
|
||||||
cols: make([]string, 0, t.FieldCount),
|
|
||||||
args: make([]any, 0, t.FieldCount),
|
|
||||||
}
|
|
||||||
return qb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *updateQry) Set(field Field, val any) UpdateClause {
|
func (q *updateQry) Set(field Field, val any) UpdateClause {
|
||||||
col := field.Name()
|
col := field.Name()
|
||||||
|
|||||||
Reference in New Issue
Block a user