diff --git a/pgm.go b/pgm.go index 635e616..2b7d6db 100644 --- a/pgm.go +++ b/pgm.go @@ -1,178 +1,44 @@ -// Patial Tech. -// Author, Ankit Patial +// pgm +// +// A simple PG query builder +// +// Author: Ankit Patial package pgm import ( + "errors" "strings" + "sync" "time" "github.com/jackc/pgx/v5/pgtype" ) -// Table in database -type Table struct { - Name string - PK []string - FieldCount uint16 - debug bool +var poolStringBuilder = sync.Pool{ + New: func() any { + return new(strings.Builder) + }, } -// Debug when set true will print generated query string in stdout -func (t Table) Debug() Clause { - t.debug = true - return t +// Errors +var ( + ErrInitTX = errors.New("failed to init db.tx") + 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) } -// -// Field ==> -// - -// Field related to a table -type Field string - -func (f Field) Name() string { - return strings.Split(string(f), ".")[1] +// put string builder back to pool +func putSB(sb *strings.Builder) { + sb.Reset() + poolStringBuilder.Put(sb) } -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 func PgTime(t time.Time) pgtype.Timestamptz { return pgtype.Timestamptz{Time: t, Valid: true} diff --git a/pgm_field.go b/pgm_field.go new file mode 100644 index 0000000..82ccf58 --- /dev/null +++ b/pgm_field.go @@ -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() +} diff --git a/pgm_table.go b/pgm_table.go new file mode 100644 index 0000000..2bf0d9f --- /dev/null +++ b/pgm_table.go @@ -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 +} diff --git a/playground/qry_delete_test.go b/playground/qry_delete_test.go index f43db6b..8e5da97 100644 --- a/playground/qry_delete_test.go +++ b/playground/qry_delete_test.go @@ -8,7 +8,7 @@ import ( ) func TestDelete(t *testing.T) { - expected := "DELETE FROM users WHERE users.id = $1 AND users.status_id NOT IN($2)" + expected := "DELETE FROM users WHERE users.id = $1 AND users.status_id != ANY($2)" got := db.User.Delete(). Where(user.ID.Eq(1), user.StatusID.NotIn(1, 2, 3)). String() diff --git a/playground/qry_insert_test.go b/playground/qry_insert_test.go index fd13af8..60a8965 100644 --- a/playground/qry_insert_test.go +++ b/playground/qry_insert_test.go @@ -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) { got := db.User.Insert(). 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 1361275 879.2 ns/op 1480 B/op 20 allocs/op func BenchmarkInsertSetMap(b *testing.B) { for b.Loop() { _ = db.User.Insert(). diff --git a/playground/qry_select_test.go b/playground/qry_select_test.go index 4f7cd8b..64d4a4b 100644 --- a/playground/qry_select_test.go +++ b/playground/qry_select_test.go @@ -40,7 +40,7 @@ func TestQryBuilder2(t *testing.T) { expected := "SELECT users.email, users.first_name FROM users JOIN user_sessions ON users.id = user_sessions.user_id" + " JOIN branch_users ON users.id = branch_users.user_id WHERE users.id = $1 AND (users.status_id = $2 OR users.updated_at = $3)" + " AND users.mfa_kind = $4 AND (users.first_name = $5 OR users.middle_name = $6) AND users.last_name != $7 AND users.phone" + - " LIKE $8 AND users.updated_at IS NOT NULL AND users.email NOT IN(SELECT users.id FROM users WHERE users.id = $9) LIMIT 10 OFFSET 100" + " LIKE $8 AND users.updated_at IS NOT NULL AND users.email != ANY(SELECT users.id FROM users WHERE users.id = $9) LIMIT 10 OFFSET 100" if expected != got { t.Errorf("\nexpected: %q\ngot: %q", expected, got) } diff --git a/playground/qry_update_test.go b/playground/qry_update_test.go index 63cf2c6..e7ebae8 100644 --- a/playground/qry_update_test.go +++ b/playground/qry_update_test.go @@ -3,7 +3,6 @@ package playground import ( "testing" - "code.patial.tech/go/pgm" "code.patial.tech/go/pgm/playground/db" "code.patial.tech/go/pgm/playground/db/user" ) @@ -27,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 1792483 670.7 ns/op 1176 B/op 20 allocs/op func BenchmarkUpdateQuery(b *testing.B) { for b.Loop() { _ = db.User.Update(). diff --git a/pool.go b/pool.go index 9fdff0b..6f4dab7 100644 --- a/pool.go +++ b/pool.go @@ -7,8 +7,6 @@ import ( "context" "errors" "log/slog" - "strings" - "sync" "sync/atomic" "time" @@ -17,17 +15,8 @@ import ( ) var ( - poolPGX atomic.Pointer[pgxpool.Pool] - poolStringBuilder = sync.Pool{ - New: func() any { - return new(strings.Builder) - }, - } - + poolPGX atomic.Pointer[pgxpool.Pool] 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 { @@ -77,17 +66,6 @@ func InitPool(conf Config) { 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() diff --git a/qry.go b/qry.go index 32a5337..64adbb6 100644 --- a/qry.go +++ b/qry.go @@ -13,150 +13,10 @@ import ( type ( Clause interface { + Insert() InsertClause Select(fields ...Field) SelectClause - // Insert() InsertSet - // Update() UpdateSet - // Delete() WhereOrExec - } - - SelectClause interface { - // Join and Inner Join are same - Join(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause - LeftJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause - RightJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause - FullJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause - CrossJoin(m Table) SelectClause - WhereClause - OrderByClause - GroupByClause - LimitClause - OffsetClause - Query - raw(prefixArgs []any) (string, []any) - } - - WhereClause interface { - Where(cond ...Conditioner) AfterWhere - } - - AfterWhere interface { - WhereClause - GroupByClause - OrderByClause - LimitClause - OffsetClause - Query - } - - GroupByClause interface { - GroupBy(fields ...Field) AfterGroupBy - } - - AfterGroupBy interface { - HavinClause - OrderByClause - LimitClause - OffsetClause - Query - } - - HavinClause interface { - Having(cond ...Conditioner) AfterHaving - } - - AfterHaving interface { - OrderByClause - LimitClause - OffsetClause - Query - } - - OrderByClause interface { - OrderBy(fields ...Field) AfterOrderBy - } - - AfterOrderBy interface { - LimitClause - OffsetClause - Query - } - - LimitClause interface { - Limit(v int) AfterLimit - } - - AfterLimit interface { - OffsetClause - Query - } - - OffsetClause interface { - Offset(v int) AfterOffset - } - - AfterOffset interface { - LimitClause - Query - } - - Conditioner interface { - Condition(args *[]any, idx int) string - } - - Insert interface { - Set(field Field, val any) InsertClause - SetMap(fields map[Field]any) InsertClause - } - - InsertClause interface { - Insert - Returning(field Field) First - OnConflict(fields ...Field) Do - Execute - Stringer - } - - Do interface { - DoNothing() Execute - DoUpdate(fields ...Field) Execute - } - - Update interface { - Set(field Field, val any) UpdateClause - SetMap(fields map[Field]any) UpdateClause - } - - UpdateClause interface { - Update - Where(cond ...Conditioner) WhereOrExec - } - - WhereOrExec interface { - Where(cond ...Conditioner) WhereOrExec - Execute - } - - Query interface { - First - All - Stringer - } - - First interface { - First(ctx context.Context, dest ...any) error - FirstTx(ctx context.Context, tx pgx.Tx, dest ...any) error - Stringer - } - - All interface { - // Query rows - // - // don't forget to close() rows - All(ctx context.Context, rows RowsCb) error - // Query rows - // - // don't forget to close() rows - AllTx(ctx context.Context, tx pgx.Tx, rows RowsCb) error + Update() UpdateClause + Delete() DeleteCluase } Execute interface { @@ -169,28 +29,11 @@ type ( String() string } - RowScanner interface { - Scan(dest ...any) error + Conditioner interface { + Condition(args *[]any, idx int) string } - - RowsCb func(row RowScanner) error ) -func joinFileds(fields []Field) string { - sb := getSB() - defer putSB(sb) - for i, f := range fields { - if i == 0 { - sb.WriteString(f.String()) - } else { - sb.WriteString(", ") - sb.WriteString(f.String()) - } - } - - return sb.String() -} - func And(cond ...Conditioner) Conditioner { return &CondGroup{op: " AND ", cond: cond} } diff --git a/qry_delete.go b/qry_delete.go index 47c7fc8..8bed06b 100644 --- a/qry_delete.go +++ b/qry_delete.go @@ -7,6 +7,10 @@ import ( ) type ( + DeleteCluase interface { + WhereOrExec + } + deleteQry struct { table string condition []Conditioner @@ -15,15 +19,6 @@ type ( } ) -func (t *Table) Delete() WhereOrExec { - qb := &deleteQry{ - table: t.Name, - debug: t.debug, - } - - return qb -} - func (q *deleteQry) Where(cond ...Conditioner) WhereOrExec { q.condition = append(q.condition, cond...) return q diff --git a/qry_insert.go b/qry_insert.go index 8fb4ed8..aa871b0 100644 --- a/qry_insert.go +++ b/qry_insert.go @@ -12,29 +12,33 @@ import ( "github.com/jackc/pgx/v5" ) -type insertQry struct { - returing *string - onConflict *string - - table string - conflictAction string - - 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, +type ( + InsertClause interface { + Insert + Returning(field Field) First + OnConflict(fields ...Field) Do + Execute + Stringer } - 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 { q.fields = append(q.fields, field.Name()) diff --git a/qry_select.go b/qry_select.go index f71843a..a82ca7f 100644 --- a/qry_select.go +++ b/qry_select.go @@ -14,6 +14,120 @@ import ( ) type ( + SelectClause interface { + // Join and Inner Join are same + Join(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause + LeftJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause + RightJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause + FullJoin(m Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause + CrossJoin(m Table) SelectClause + WhereClause + OrderByClause + GroupByClause + LimitClause + OffsetClause + Query + raw(prefixArgs []any) (string, []any) + } + + WhereClause interface { + Where(cond ...Conditioner) AfterWhere + } + + AfterWhere interface { + WhereClause + GroupByClause + OrderByClause + LimitClause + OffsetClause + Query + } + + GroupByClause interface { + GroupBy(fields ...Field) AfterGroupBy + } + + AfterGroupBy interface { + HavinClause + OrderByClause + LimitClause + OffsetClause + Query + } + + HavinClause interface { + Having(cond ...Conditioner) AfterHaving + } + + AfterHaving interface { + OrderByClause + LimitClause + OffsetClause + Query + } + + OrderByClause interface { + OrderBy(fields ...Field) AfterOrderBy + } + + AfterOrderBy interface { + LimitClause + OffsetClause + Query + } + + LimitClause interface { + Limit(v int) AfterLimit + } + + AfterLimit interface { + OffsetClause + Query + } + + OffsetClause interface { + Offset(v int) AfterOffset + } + + AfterOffset interface { + LimitClause + Query + } + + Do interface { + DoNothing() Execute + DoUpdate(fields ...Field) Execute + } + + Query interface { + First + All + Stringer + } + + 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 { table string fields []Field @@ -23,9 +137,11 @@ type ( groupBy []Field having []Conditioner orderBy []Field - limit int - offset int - debug bool + + limit int + offset int + + debug bool } CondAction uint8 @@ -51,17 +167,6 @@ const ( CondActionSubQuery ) -// Select clause -func (t Table) Select(field ...Field) SelectClause { - qb := &selectQry{ - table: t.Name, - debug: t.debug, - fields: field, - } - - return qb -} - func (q *selectQry) Join(t Table, t1Field, t2Field Field, cond ...Conditioner) SelectClause { return q.buildJoin(t, "JOIN", t1Field, t2Field, cond...) } diff --git a/qry_update.go b/qry_update.go index 47def29..6c29f64 100644 --- a/qry_update.go +++ b/qry_update.go @@ -11,23 +11,30 @@ import ( "github.com/jackc/pgx/v5" ) -type updateQry struct { - table string - cols []string - condition []Conditioner - args []any - debug bool -} - -func (t *Table) Update() Update { - qb := &updateQry{ - table: t.Name, - debug: t.debug, - cols: make([]string, 0, t.FieldCount), - args: make([]any, 0, t.FieldCount), +type ( + Update interface { + Set(field Field, val any) UpdateClause + SetMap(fields map[Field]any) UpdateClause } - 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 { col := field.Name()