Compare commits
	
		
			7 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a795c0e8d6 | |||
| bb6a45732f | |||
| 2551e07b3e | |||
| 9837fb1e37 | |||
| 12d6fface6 | |||
| 325103e8ef | |||
| 6f5748d3d3 | 
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,7 +1,10 @@
 | 
			
		||||
.PHONY: run bench-select test
 | 
			
		||||
 | 
			
		||||
run:
 | 
			
		||||
	go run ./cmd -o ./example/db ./example/schema.sql
 | 
			
		||||
	go run ./cmd -o ./playground/db ./playground/schema.sql
 | 
			
		||||
 | 
			
		||||
bench-select:
 | 
			
		||||
	go test ./example -bench BenchmarkSelect -memprofile memprofile.out -cpuprofile profile.out
 | 
			
		||||
	go test ./playground -bench BenchmarkSelect -memprofile memprofile.out -cpuprofile profile.out
 | 
			
		||||
 | 
			
		||||
test:
 | 
			
		||||
	go test ./playground
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,18 @@ func generate(scheamPath, outDir string) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	code, err := formatGoCode(sb.String())
 | 
			
		||||
	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("import %q\n\n", "code.patial.tech/go/pgm"))
 | 
			
		||||
	sb.WriteString("const (")
 | 
			
		||||
	sb.WriteString("\n // All fields in table " + tblName)
 | 
			
		||||
	sb.WriteString(fmt.Sprintf("\n All pgm.Field = %q", tblName+".*"))
 | 
			
		||||
	var name string
 | 
			
		||||
	for _, c := range cols {
 | 
			
		||||
		name = strings.ReplaceAll(c.Name, "_", " ")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										249
									
								
								pgm.go
									
									
									
									
									
								
							
							
						
						
									
										249
									
								
								pgm.go
									
									
									
									
									
								
							@@ -1,178 +1,103 @@
 | 
			
		||||
// Patial Tech.
 | 
			
		||||
// Author, Ankit Patial
 | 
			
		||||
// pgm
 | 
			
		||||
//
 | 
			
		||||
// A simple PG string query builder
 | 
			
		||||
//
 | 
			
		||||
// Author: Ankit Patial
 | 
			
		||||
 | 
			
		||||
package pgm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/jackc/pgx/v5"
 | 
			
		||||
	"github.com/jackc/pgx/v5/pgtype"
 | 
			
		||||
	"github.com/jackc/pgx/v5/pgxpool"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Table in database
 | 
			
		||||
type Table struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	PK         []string
 | 
			
		||||
	FieldCount uint16
 | 
			
		||||
	debug      bool
 | 
			
		||||
var (
 | 
			
		||||
	poolPGX              atomic.Pointer[pgxpool.Pool]
 | 
			
		||||
	ErrConnStringMissing = errors.New("connection string is empty")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
func (t Table) Debug() Clause {
 | 
			
		||||
	t.debug = true
 | 
			
		||||
	return t
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  Field ==>
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Field related to a table
 | 
			
		||||
type Field string
 | 
			
		||||
 | 
			
		||||
func (f Field) Name() string {
 | 
			
		||||
	return strings.Split(string(f), ".")[1]
 | 
			
		||||
// GetPool instance
 | 
			
		||||
func GetPool() *pgxpool.Pool {
 | 
			
		||||
	return poolPGX.Load()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) String() string {
 | 
			
		||||
	return string(f)
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Count fn wrapping of field
 | 
			
		||||
func (f Field) Count() Field {
 | 
			
		||||
	return Field("COUNT(" + f.String() + ")")
 | 
			
		||||
// IsNotFound error check
 | 
			
		||||
func IsNotFound(err error) bool {
 | 
			
		||||
	return errors.Is(err, pgx.ErrNoRows)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StringEscape will return a empty string for null value
 | 
			
		||||
func (f Field) StringEscape() Field {
 | 
			
		||||
	return Field("COALESCE(" + f.String() + ", '')")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NumberEscape will return a zero string for null value
 | 
			
		||||
func (f Field) NumberEscape() Field {
 | 
			
		||||
	return Field("COALESCE(" + f.String() + ", 0)")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BooleanEscape will return a false for null value
 | 
			
		||||
func (f Field) BooleanEscape() Field {
 | 
			
		||||
	return Field("COALESCE(" + f.String() + ", FALSE)")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Avg fn wrapping of field
 | 
			
		||||
func (f Field) Avg() Field {
 | 
			
		||||
	return Field("AVG(" + f.String() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) Sum() Field {
 | 
			
		||||
	return Field("SUM(" + f.String() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) Max() Field {
 | 
			
		||||
	return Field("MAX(" + f.String() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) Min() Field {
 | 
			
		||||
	return Field("Min(" + f.String() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) Lower() Field {
 | 
			
		||||
	return Field("LOWER(" + f.String() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) Upper() Field {
 | 
			
		||||
	return Field("UPPER(" + f.String() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// In using ANY
 | 
			
		||||
func (f Field) In(val ...any) Conditioner {
 | 
			
		||||
	col := f.String()
 | 
			
		||||
	return &Cond{Field: col, Val: val, op: " ANY($", action: CondActionNeedToClose, len: len(col) + 5}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotIn using ANY
 | 
			
		||||
func (f Field) NotIn(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}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  Helper func ==>
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// PgTime as in UTC
 | 
			
		||||
func PgTime(t time.Time) pgtype.Timestamptz {
 | 
			
		||||
	return pgtype.Timestamptz{Time: t, Valid: true}
 | 
			
		||||
@@ -181,15 +106,3 @@ func PgTime(t time.Time) pgtype.Timestamptz {
 | 
			
		||||
func PgTimeNow() pgtype.Timestamptz {
 | 
			
		||||
	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 + "')")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										247
									
								
								pgm_field.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								pgm_field.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,247 @@
 | 
			
		||||
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() + ")")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 + "')")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f Field) TsRank(query, as string) Field {
 | 
			
		||||
	return Field("TS_RANK(" + f.String() + ", " + query + ") 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 (f Field) TsQuery(as string) Conditioner {
 | 
			
		||||
	col := f.String()
 | 
			
		||||
	return &Cond{Field: col, op: " @@ " + as, len: len(col) + 5}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								pgm_table.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								pgm_table.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
package pgm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Table in database
 | 
			
		||||
type Table struct {
 | 
			
		||||
	tsQuery   *string
 | 
			
		||||
	tsQueryAs *string
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Table) WithTsQuery(q, as string) *Table {
 | 
			
		||||
	t.tsQuery = &q
 | 
			
		||||
	t.tsQueryAs = &as
 | 
			
		||||
	return t
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.tsQuery != nil {
 | 
			
		||||
		var as string
 | 
			
		||||
		if t.tsQueryAs != nil && *t.tsQueryAs != "" {
 | 
			
		||||
			as = *t.tsQueryAs
 | 
			
		||||
		} else {
 | 
			
		||||
			// add default as field
 | 
			
		||||
			as = "query"
 | 
			
		||||
		}
 | 
			
		||||
		qb.args = append(qb.args, as)
 | 
			
		||||
		qb.table += ", TO_TSQUERY('english', $" + strconv.Itoa(len(qb.args)) + ") " + as
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// All fields in table branch_users
 | 
			
		||||
	All pgm.Field = "branch_users.*"
 | 
			
		||||
	// BranchID field has db type "bigint NOT NULL"
 | 
			
		||||
	BranchID pgm.Field = "branch_users.branch_id"
 | 
			
		||||
	// 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
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// All fields in table comments
 | 
			
		||||
	All pgm.Field = "comments.*"
 | 
			
		||||
	// ID field has db type "integer NOT NULL"
 | 
			
		||||
	ID pgm.Field = "comments.id"
 | 
			
		||||
	// 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
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// All fields in table employees
 | 
			
		||||
	All pgm.Field = "employees.*"
 | 
			
		||||
	// ID field has db type "integer NOT NULL"
 | 
			
		||||
	ID pgm.Field = "employees.id"
 | 
			
		||||
	// 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
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// All fields in table posts
 | 
			
		||||
	All pgm.Field = "posts.*"
 | 
			
		||||
	// ID field has db type "integer NOT NULL"
 | 
			
		||||
	ID pgm.Field = "posts.id"
 | 
			
		||||
	// UserID field has db type "integer NOT NULL"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,22 @@
 | 
			
		||||
// Code generated by code.patial.tech/go/pgm/cmd
 | 
			
		||||
// DO NOT EDIT.
 | 
			
		||||
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
 | 
			
		||||
 | 
			
		||||
package db
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	User        = pgm.Table{Name: "users", FieldCount: 11}
 | 
			
		||||
	User        = pgm.Table{Name: "users", FieldCount: 12}
 | 
			
		||||
	UserSession = pgm.Table{Name: "user_sessions", FieldCount: 8}
 | 
			
		||||
	BranchUser  = pgm.Table{Name: "branch_users", FieldCount: 5}
 | 
			
		||||
	Post        = pgm.Table{Name: "posts", FieldCount: 5}
 | 
			
		||||
	Comment     = pgm.Table{Name: "comments", 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
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// All fields in table users
 | 
			
		||||
	All pgm.Field = "users.*"
 | 
			
		||||
	// ID field has db type "integer NOT NULL"
 | 
			
		||||
	ID pgm.Field = "users.id"
 | 
			
		||||
	// Name field has db type "character varying(255) NOT NULL"
 | 
			
		||||
@@ -23,6 +25,8 @@ const (
 | 
			
		||||
	StatusID pgm.Field = "users.status_id"
 | 
			
		||||
	// MfaKind field has db type "character varying(50) DEFAULT 'None'::character varying"
 | 
			
		||||
	MfaKind pgm.Field = "users.mfa_kind"
 | 
			
		||||
	// SearchVector field has db type "tsvector"
 | 
			
		||||
	SearchVector pgm.Field = "users.search_vector"
 | 
			
		||||
	// CreatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
			
		||||
	CreatedAt pgm.Field = "users.created_at"
 | 
			
		||||
	// UpdatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
import "code.patial.tech/go/pgm"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// All fields in table user_sessions
 | 
			
		||||
	All pgm.Field = "user_sessions.*"
 | 
			
		||||
	// ID field has db type "character varying NOT NULL"
 | 
			
		||||
	ID pgm.Field = "user_sessions.id"
 | 
			
		||||
	// CreatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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().
 | 
			
		||||
		Where(user.ID.Eq(1), user.StatusID.NotIn(1, 2, 3)).
 | 
			
		||||
		Where(user.ID.Eq(1), user.StatusID.NotAny(1, 2, 3)).
 | 
			
		||||
		String()
 | 
			
		||||
	if got != expected {
 | 
			
		||||
		t.Errorf("got %q, want %q", got, expected)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,24 +17,7 @@ func TestInsertQuery(t *testing.T) {
 | 
			
		||||
		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 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"
 | 
			
		||||
	expected := "INSERT INTO users(email, phone, first_name, last_name) VALUES($1, $2, $3, $4) RETURNING users.id"
 | 
			
		||||
	if got != expected {
 | 
			
		||||
		t.Errorf("\nexpected: %q\ngot: %q", expected, got)
 | 
			
		||||
	}
 | 
			
		||||
@@ -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) {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
		_ = db.User.Insert().
 | 
			
		||||
 
 | 
			
		||||
@@ -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" +
 | 
			
		||||
		" 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" +
 | 
			
		||||
		" 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 {
 | 
			
		||||
		t.Errorf("\nexpected: %q\ngot: %q", expected, got)
 | 
			
		||||
	}
 | 
			
		||||
@@ -85,6 +85,46 @@ func TestSelectWithJoin(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 TestSelectTV(t *testing.T) {
 | 
			
		||||
	expected := "SELECT users.first_name, users.last_name, users.email, TS_RANK(users.search_vector, query) AS rank" +
 | 
			
		||||
		" FROM users, TO_TSQUERY('english', $1) query" +
 | 
			
		||||
		" WHERE users.status_id = $2 AND users.search_vector @@ query" +
 | 
			
		||||
		" ORDER BY rank DESC"
 | 
			
		||||
 | 
			
		||||
	qry := db.User.
 | 
			
		||||
		WithTsQuery("anki", "query").
 | 
			
		||||
		Select(user.FirstName, user.LastName, user.Email, user.SearchVector.TsRank("query", "rank")).
 | 
			
		||||
		Where(user.StatusID.Eq(1), user.SearchVector.TsQuery("query")).
 | 
			
		||||
		OrderBy(pgm.Field("rank").Desc())
 | 
			
		||||
 | 
			
		||||
	got := qry.String()
 | 
			
		||||
 | 
			
		||||
	if expected != got {
 | 
			
		||||
		t.Errorf("\nexpected: %q\n\ngot:      %q", expected, got)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BenchmarkSelect-12    	  638901	      1860 ns/op	    4266 B/op	      61 allocs/op
 | 
			
		||||
func BenchmarkSelect(b *testing.B) {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ package playground
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.patial.tech/go/pgm"
 | 
			
		||||
	"code.patial.tech/go/pgm/playground/db"
 | 
			
		||||
	"code.patial.tech/go/pgm/playground/db/user"
 | 
			
		||||
)
 | 
			
		||||
@@ -27,27 +26,6 @@ func TestUpdateQuery(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BenchmarkUpdateQuery-12    	 2004985	       592.2 ns/op	    1176 B/op	      20 allocs/op
 | 
			
		||||
func BenchmarkUpdateQuery(b *testing.B) {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,7 @@ CREATE TABLE public.users (
 | 
			
		||||
    last_name character varying(50) NOT NULL,
 | 
			
		||||
    status_id smallint,
 | 
			
		||||
    mfa_kind character varying(50) DEFAULT 'None'::character varying,
 | 
			
		||||
    search_vector tsvector,
 | 
			
		||||
    created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
    updated_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										179
									
								
								qry.go
									
									
									
									
									
								
							
							
						
						
									
										179
									
								
								qry.go
									
									
									
									
									
								
							@@ -7,156 +7,17 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/jackc/pgx/v5"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	Clause interface {
 | 
			
		||||
		Insert() InsertClause
 | 
			
		||||
		Select(fields ...Field) SelectClause
 | 
			
		||||
		// Insert() InsertSet
 | 
			
		||||
		// Update() UpdateSet
 | 
			
		||||
		// Delete() WhereOrExec
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
		Update() UpdateClause
 | 
			
		||||
		Delete() DeleteCluase
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Execute interface {
 | 
			
		||||
@@ -169,26 +30,26 @@ type (
 | 
			
		||||
		String() string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RowScanner interface {
 | 
			
		||||
		Scan(dest ...any) error
 | 
			
		||||
	Conditioner interface {
 | 
			
		||||
		Condition(args *[]any, idx int) string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RowsCb func(row RowScanner) error
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
var sbPool = sync.Pool{
 | 
			
		||||
	New: func() any {
 | 
			
		||||
		return new(strings.Builder)
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,10 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	DeleteCluase interface {
 | 
			
		||||
		WhereOrExec
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deleteQry struct {
 | 
			
		||||
		table     string
 | 
			
		||||
		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 {
 | 
			
		||||
	q.condition = append(q.condition, cond...)
 | 
			
		||||
	return q
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,21 @@ import (
 | 
			
		||||
	"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
 | 
			
		||||
		onConflict *string
 | 
			
		||||
 | 
			
		||||
@@ -23,18 +37,8 @@ type insertQry struct {
 | 
			
		||||
		vals   []string
 | 
			
		||||
		args   []any
 | 
			
		||||
		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 {
 | 
			
		||||
	q.fields = append(q.fields, field.Name())
 | 
			
		||||
@@ -51,7 +55,7 @@ func (q *insertQry) SetMap(cols map[Field]any) InsertClause {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *insertQry) Returning(field Field) First {
 | 
			
		||||
	col := field.Name()
 | 
			
		||||
	col := field.String()
 | 
			
		||||
	q.returing = &col
 | 
			
		||||
	return q
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										153
									
								
								qry_select.go
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								qry_select.go
									
									
									
									
									
								
							@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@@ -14,6 +15,125 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		table   string
 | 
			
		||||
		fields  []Field
 | 
			
		||||
@@ -23,8 +143,10 @@ type (
 | 
			
		||||
		groupBy []Field
 | 
			
		||||
		having  []Conditioner
 | 
			
		||||
		orderBy []Field
 | 
			
		||||
 | 
			
		||||
		limit  int
 | 
			
		||||
		offset int
 | 
			
		||||
 | 
			
		||||
		debug bool
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -51,17 +173,6 @@ const (
 | 
			
		||||
	CondActionSubQuery
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Select clause
 | 
			
		||||
func (t Table) Select(field ...Field) SelectClause {
 | 
			
		||||
	qb := &selectQry{
 | 
			
		||||
		table:  t.Name,
 | 
			
		||||
		debug:  t.debug,
 | 
			
		||||
		fields: field,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return qb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *selectQry) Join(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
 | 
			
		||||
	return q.buildJoin(t, "JOIN", t1Field, t2Field, cond...)
 | 
			
		||||
}
 | 
			
		||||
@@ -186,6 +297,17 @@ func (q *selectQry) raw(prefixArgs []any) (string, []any) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
	defer putSB(sb)
 | 
			
		||||
 | 
			
		||||
@@ -251,14 +373,19 @@ func (q *selectQry) String() string {
 | 
			
		||||
		sb.WriteString(strconv.Itoa(q.offset))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qry := sb.String()
 | 
			
		||||
	qry = sb.String()
 | 
			
		||||
	if q.debug {
 | 
			
		||||
		fmt.Println("***")
 | 
			
		||||
		fmt.Println(qry)
 | 
			
		||||
		fmt.Printf("%+v\n", q.args)
 | 
			
		||||
		fmt.Println("***")
 | 
			
		||||
	}
 | 
			
		||||
	return qry
 | 
			
		||||
 | 
			
		||||
	if needArgs {
 | 
			
		||||
		args = slices.Clone(q.args)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (q *selectQry) averageLen() int {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,23 +11,30 @@ import (
 | 
			
		||||
	"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
 | 
			
		||||
		cols      []string
 | 
			
		||||
		condition []Conditioner
 | 
			
		||||
		args      []any
 | 
			
		||||
		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 {
 | 
			
		||||
	col := field.Name()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user