restructuring of files. added 2 new methods(Asc, Desc) to field
This commit is contained in:
184
pgm.go
184
pgm.go
@@ -1,178 +1,44 @@
|
|||||||
// Patial Tech.
|
// pgm
|
||||||
// Author, Ankit Patial
|
//
|
||||||
|
// A simple PG query builder
|
||||||
|
//
|
||||||
|
// Author: Ankit Patial
|
||||||
|
|
||||||
package pgm
|
package pgm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Table in database
|
var poolStringBuilder = sync.Pool{
|
||||||
type Table struct {
|
New: func() any {
|
||||||
Name string
|
return new(strings.Builder)
|
||||||
PK []string
|
},
|
||||||
FieldCount uint16
|
|
||||||
debug bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug when set true will print generated query string in stdout
|
// Errors
|
||||||
func (t Table) Debug() Clause {
|
var (
|
||||||
t.debug = true
|
ErrInitTX = errors.New("failed to init db.tx")
|
||||||
return t
|
ErrCommitTX = errors.New("failed to commit db.tx")
|
||||||
|
ErrNoRows = errors.New("no data found")
|
||||||
|
)
|
||||||
|
|
||||||
|
// get string builder from pool
|
||||||
|
func getSB() *strings.Builder {
|
||||||
|
return poolStringBuilder.Get().(*strings.Builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// put string builder back to pool
|
||||||
// Field ==>
|
func putSB(sb *strings.Builder) {
|
||||||
//
|
sb.Reset()
|
||||||
|
poolStringBuilder.Put(sb)
|
||||||
// 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 fn wrapping of field
|
|
||||||
func (f Field) Count() Field {
|
|
||||||
return Field("COUNT(" + f.String() + ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
// 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}
|
||||||
|
|||||||
167
pgm_field.go
Normal file
167
pgm_field.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
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 fn wrapping of field
|
||||||
|
func (f Field) Count() Field {
|
||||||
|
return Field("COUNT(" + f.String() + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) Asc() Field {
|
||||||
|
return Field(f.String() + " ASC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Field) Desc() Field {
|
||||||
|
return Field(f.String() + " DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
59
pgm_table.go
Normal file
59
pgm_table.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package pgm
|
||||||
|
|
||||||
|
// Table in database
|
||||||
|
type Table struct {
|
||||||
|
Name string
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
table: t.Name,
|
||||||
|
fields: field,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ 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.NotIn(1, 2, 3)).
|
||||||
String()
|
String()
|
||||||
|
|||||||
@@ -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").
|
||||||
@@ -68,6 +51,7 @@ func BenchmarkInsertQuery(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BenchmarkInsertSetMap-12 1534039 777.1 ns/op 1480 B/op 20 allocs/op
|
// BenchmarkInsertSetMap-12 1534039 777.1 ns/op 1480 B/op 20 allocs/op
|
||||||
|
// BenchmarkInsertSetMap-12 1361275 879.2 ns/op 1480 B/op 20 allocs/op
|
||||||
func BenchmarkInsertSetMap(b *testing.B) {
|
func BenchmarkInsertSetMap(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
_ = db.User.Insert().
|
_ = 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" +
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -27,28 +26,8 @@ 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
|
// BenchmarkUpdateQuery-12 2004985 592.2 ns/op 1176 B/op 20 allocs/op
|
||||||
|
// BenchmarkUpdateQuery-12 1792483 670.7 ns/op 1176 B/op 20 allocs/op
|
||||||
func BenchmarkUpdateQuery(b *testing.B) {
|
func BenchmarkUpdateQuery(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
_ = db.User.Update().
|
_ = db.User.Update().
|
||||||
|
|||||||
24
pool.go
24
pool.go
@@ -7,8 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -17,17 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
poolPGX atomic.Pointer[pgxpool.Pool]
|
poolPGX atomic.Pointer[pgxpool.Pool]
|
||||||
poolStringBuilder = sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return new(strings.Builder)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrConnStringMissing = errors.New("connection string is empty")
|
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 {
|
type Config struct {
|
||||||
@@ -77,17 +66,6 @@ func InitPool(conf Config) {
|
|||||||
poolPGX.Store(p)
|
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
|
// GetPool instance
|
||||||
func GetPool() *pgxpool.Pool {
|
func GetPool() *pgxpool.Pool {
|
||||||
return poolPGX.Load()
|
return poolPGX.Load()
|
||||||
|
|||||||
167
qry.go
167
qry.go
@@ -13,150 +13,10 @@ import (
|
|||||||
|
|
||||||
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, 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Execute interface {
|
Execute interface {
|
||||||
@@ -169,28 +29,11 @@ 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 {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
func And(cond ...Conditioner) Conditioner {
|
func And(cond ...Conditioner) Conditioner {
|
||||||
return &CondGroup{op: " AND ", cond: cond}
|
return &CondGroup{op: " AND ", cond: cond}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
133
qry_select.go
133
qry_select.go
@@ -14,6 +14,120 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
selectQry struct {
|
selectQry struct {
|
||||||
table string
|
table string
|
||||||
fields []Field
|
fields []Field
|
||||||
@@ -23,9 +137,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,17 +167,6 @@ const (
|
|||||||
CondActionSubQuery
|
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 {
|
func (q *selectQry) Join(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause {
|
||||||
return q.buildJoin(t, "JOIN", t1Field, t2Field, cond...)
|
return q.buildJoin(t, "JOIN", t1Field, t2Field, cond...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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