Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,6 +95,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, "_", " ")
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										241
									
								
								pgm.go
									
									
									
									
									
								
							
							
						
						
									
										241
									
								
								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)
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//
 | 
					// GetPool instance
 | 
				
			||||||
//  Field ==>
 | 
					func GetPool() *pgxpool.Pool {
 | 
				
			||||||
//
 | 
						return poolPGX.Load()
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Field related to a table
 | 
					 | 
				
			||||||
type Field string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f Field) Name() string {
 | 
					 | 
				
			||||||
	return strings.Split(string(f), ".")[1]
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f Field) String() string {
 | 
					// BeginTx begins a pgx poll transaction
 | 
				
			||||||
	return string(f)
 | 
					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
 | 
					// IsNotFound error check
 | 
				
			||||||
func (f Field) Count() Field {
 | 
					func IsNotFound(err error) bool {
 | 
				
			||||||
	return Field("COUNT(" + f.String() + ")")
 | 
						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}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 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"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								playground/db/derived.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								playground/db/derived.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										179
									
								
								qry.go
									
									
									
									
									
								
							
							
						
						
									
										179
									
								
								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,29 +12,33 @@ import (
 | 
				
			|||||||
	"github.com/jackc/pgx/v5"
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type insertQry struct {
 | 
					type (
 | 
				
			||||||
	returing   *string
 | 
						InsertClause interface {
 | 
				
			||||||
	onConflict *string
 | 
							Insert
 | 
				
			||||||
 | 
							Returning(field Field) First
 | 
				
			||||||
	table          string
 | 
							OnConflict(fields ...Field) Do
 | 
				
			||||||
	conflictAction string
 | 
							Execute
 | 
				
			||||||
 | 
							Stringer
 | 
				
			||||||
	fields []string
 | 
					 | 
				
			||||||
	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
 | 
					
 | 
				
			||||||
}
 | 
						Insert interface {
 | 
				
			||||||
 | 
							Set(field Field, val any) InsertClause
 | 
				
			||||||
 | 
							SetMap(fields map[Field]any) InsertClause
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						insertQry struct {
 | 
				
			||||||
 | 
							returing   *string
 | 
				
			||||||
 | 
							onConflict *string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							table          string
 | 
				
			||||||
 | 
							conflictAction string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fields []string
 | 
				
			||||||
 | 
							vals   []string
 | 
				
			||||||
 | 
							args   []any
 | 
				
			||||||
 | 
							debug  bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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())
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										206
									
								
								qry_select.go
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								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,9 +143,11 @@ type (
 | 
				
			|||||||
		groupBy []Field
 | 
							groupBy []Field
 | 
				
			||||||
		having  []Conditioner
 | 
							having  []Conditioner
 | 
				
			||||||
		orderBy []Field
 | 
							orderBy []Field
 | 
				
			||||||
		limit   int
 | 
					
 | 
				
			||||||
		offset  int
 | 
							limit  int
 | 
				
			||||||
		debug   bool
 | 
							offset int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							debug bool
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CondAction uint8
 | 
						CondAction uint8
 | 
				
			||||||
@@ -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,
 | 
					func (q *selectQry) LeftJoin(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
 | 
				
			||||||
		fields: field,
 | 
						return q.buildJoin(t, "LEFT JOIN", t1Field, t2Field, cond...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) RightJoin(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
 | 
				
			||||||
 | 
						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 qb
 | 
						// Join has condition(s)
 | 
				
			||||||
}
 | 
						sb := getSB()
 | 
				
			||||||
 | 
						defer putSB(sb)
 | 
				
			||||||
 | 
						sb.Grow(len(str) * 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (q *selectQry) Join(t Table, t1Field, t2Field Field) SelectClause {
 | 
						sb.WriteString(str + " AND ")
 | 
				
			||||||
	q.join = append(q.join, "JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
 | 
					 | 
				
			||||||
	return q
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (q *selectQry) LeftJoin(t Table, t1Field, t2Field Field) SelectClause {
 | 
						var argIdx int
 | 
				
			||||||
	q.join = append(q.join, "LEFT JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
 | 
						for i, c := range cond {
 | 
				
			||||||
	return q
 | 
							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 (
 | 
				
			||||||
	table     string
 | 
						Update interface {
 | 
				
			||||||
	cols      []string
 | 
							Set(field Field, val any) UpdateClause
 | 
				
			||||||
	condition []Conditioner
 | 
							SetMap(fields map[Field]any) UpdateClause
 | 
				
			||||||
	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
 | 
					
 | 
				
			||||||
}
 | 
						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 (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