table all field

This commit is contained in:
2025-10-18 14:43:42 +05:30
parent 325103e8ef
commit 12d6fface6
16 changed files with 149 additions and 137 deletions

View File

@@ -1,5 +1,5 @@
run:
go run ./cmd -o ./example/db ./example/schema.sql
go run ./cmd -o ./playground/db ./playground/schema.sql
bench-select:
go test ./example -bench BenchmarkSelect -memprofile memprofile.out -cpuprofile profile.out

View File

@@ -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("import %q\n\n", "code.patial.tech/go/pgm"))
sb.WriteString("const (")
sb.WriteString("\n // All fields in table " + tblName)
sb.WriteString(fmt.Sprintf("\n All pgm.Field = %q", tblName+".*"))
var name string
for _, c := range cols {
name = strings.ReplaceAll(c.Name, "_", " ")

101
pgm.go
View File

@@ -1,25 +1,27 @@
// pgm
//
// A simple PG query builder
// A simple PG string query builder
//
// Author: Ankit Patial
package pgm
import (
"context"
"errors"
"strings"
"sync"
"log/slog"
"sync/atomic"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
)
var poolStringBuilder = sync.Pool{
New: func() any {
return new(strings.Builder)
},
}
var (
poolPGX atomic.Pointer[pgxpool.Pool]
ErrConnStringMissing = errors.New("connection string is empty")
)
// Errors
var (
@@ -28,15 +30,72 @@ var (
ErrNoRows = errors.New("no data found")
)
// get string builder from pool
func getSB() *strings.Builder {
return poolStringBuilder.Get().(*strings.Builder)
type Config struct {
ConnString string
MaxConns int32
MinConns int32
MaxConnLifetime time.Duration
MaxConnIdleTime time.Duration
}
// put string builder back to pool
func putSB(sb *strings.Builder) {
sb.Reset()
poolStringBuilder.Put(sb)
// 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)
}
// 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)
}
// PgTime as in UTC
@@ -47,15 +106,3 @@ func PgTime(t time.Time) pgtype.Timestamptz {
func PgTimeNow() pgtype.Timestamptz {
return pgtype.Timestamptz{Time: time.Now(), Valid: true}
}
func ConcatWs(sep string, fields ...Field) Field {
return Field("concat_ws('" + sep + "'," + joinFileds(fields) + ")")
}
func StringAgg(exp, sep string) Field {
return Field("string_agg(" + exp + ",'" + sep + "')")
}
func StringAggCast(exp, sep string) Field {
return Field("string_agg(cast(" + exp + " as varchar),'" + sep + "')")
}

View File

@@ -13,59 +13,73 @@ func (f Field) String() string {
return string(f)
}
// Count fn wrapping of field
// Count function wrapped field
func (f Field) Count() Field {
return Field("COUNT(" + f.String() + ")")
}
// StringEscape will return a empty string for null value
// StringEscape will wrap field with:
//
// COALESCE(field, ”)
func (f Field) StringEscape() Field {
return Field("COALESCE(" + f.String() + ", '')")
}
// NumberEscape will return a zero string for null value
// NumberEscape will wrap field with:
//
// COALESCE(field, 0)
func (f Field) NumberEscape() Field {
return Field("COALESCE(" + f.String() + ", 0)")
}
// BooleanEscape will return a false for null value
// BooleanEscape will wrap field with:
//
// COALESCE(field, FALSE)
func (f Field) BooleanEscape() Field {
return Field("COALESCE(" + f.String() + ", FALSE)")
}
// Avg fn wrapping of field
// 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")
}
@@ -133,14 +147,12 @@ func (f Field) ILike(val string) Conditioner {
return &Cond{Field: col, Val: val, op: " ILIKE $", len: len(col) + 5}
}
// In using ANY
func (f Field) In(val ...any) Conditioner {
func (f Field) Any(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 {
func (f Field) NotAny(val ...any) Conditioner {
col := f.String()
return &Cond{Field: col, Val: val, op: " != ANY($", action: CondActionNeedToClose, len: len(col) + 5}
}
@@ -151,6 +163,18 @@ func (f Field) NotInSubQuery(qry WhereClause) Conditioner {
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)

View File

@@ -1,10 +1,12 @@
// Code generated by db-gen. DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package branchuser
import "code.patial.tech/go/pgm"
const (
// All fields in table branch_users
All pgm.Field = "branch_users.*"
// BranchID field has db type "bigint NOT NULL"
BranchID pgm.Field = "branch_users.branch_id"
// UserID field has db type "bigint NOT NULL"

View File

@@ -1,10 +1,12 @@
// Code generated by db-gen. DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package comment
import "code.patial.tech/go/pgm"
const (
// All fields in table comments
All pgm.Field = "comments.*"
// ID field has db type "integer NOT NULL"
ID pgm.Field = "comments.id"
// PostID field has db type "integer NOT NULL"

View File

@@ -1,10 +1,12 @@
// Code generated by db-gen. DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package employee
import "code.patial.tech/go/pgm"
const (
// All fields in table employees
All pgm.Field = "employees.*"
// ID field has db type "integer NOT NULL"
ID pgm.Field = "employees.id"
// Name field has db type "var NOT NULL"

View File

@@ -1,10 +1,12 @@
// Code generated by db-gen. DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package post
import "code.patial.tech/go/pgm"
const (
// All fields in table posts
All pgm.Field = "posts.*"
// ID field has db type "integer NOT NULL"
ID pgm.Field = "posts.id"
// UserID field has db type "integer NOT NULL"

View File

@@ -1,5 +1,4 @@
// Code generated by code.patial.tech/go/pgm/cmd
// DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package db

View File

@@ -1,10 +1,12 @@
// Code generated by db-gen. DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package user
import "code.patial.tech/go/pgm"
const (
// All fields in table users
All pgm.Field = "users.*"
// ID field has db type "integer NOT NULL"
ID pgm.Field = "users.id"
// Name field has db type "character varying(255) NOT NULL"

View File

@@ -1,10 +1,12 @@
// Code generated by db-gen. DO NOT EDIT.
// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.
package usersession
import "code.patial.tech/go/pgm"
const (
// All fields in table user_sessions
All pgm.Field = "user_sessions.*"
// ID field has db type "character varying NOT NULL"
ID pgm.Field = "user_sessions.id"
// CreatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"

View File

@@ -10,7 +10,7 @@ import (
func TestDelete(t *testing.T) {
expected := "DELETE FROM users WHERE users.id = $1 AND users.status_id != ANY($2)"
got := db.User.Delete().
Where(user.ID.Eq(1), user.StatusID.NotIn(1, 2, 3)).
Where(user.ID.Eq(1), user.StatusID.NotAny(1, 2, 3)).
String()
if got != expected {
t.Errorf("got %q, want %q", got, expected)

View File

@@ -36,7 +36,7 @@ func TestInsertQuery2(t *testing.T) {
}
}
// BenchmarkInsertQuery-12 1952412 605.3 ns/op 1144 B/op 18 allocs/op
// BenchmarkInsertQuery-12 2014519 584.0 ns/op 1144 B/op 18 allocs/op
func BenchmarkInsertQuery(b *testing.B) {
for b.Loop() {
_ = db.User.Insert().
@@ -51,7 +51,6 @@ 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().

View File

@@ -27,7 +27,6 @@ func TestUpdateQuery(t *testing.T) {
}
// 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().

88
pool.go
View File

@@ -1,88 +0,0 @@
// Patial Tech.
// Author, Ankit Patial
package pgm
import (
"context"
"errors"
"log/slog"
"sync/atomic"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
poolPGX atomic.Pointer[pgxpool.Pool]
ErrConnStringMissing = errors.New("connection string is empty")
)
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)
}
// 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)
}

18
qry.go
View File

@@ -7,6 +7,7 @@ import (
"context"
"strconv"
"strings"
"sync"
"github.com/jackc/pgx/v5"
)
@@ -34,6 +35,23 @@ type (
}
)
var sbPool = sync.Pool{
New: func() any {
return new(strings.Builder)
},
}
// 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 {
return &CondGroup{op: " AND ", cond: cond}
}