first commit
This commit is contained in:
		
							
								
								
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					# ---> Go
 | 
				
			||||||
 | 
					# If you prefer the allow list template instead of the deny list, see community template:
 | 
				
			||||||
 | 
					# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Binaries for programs and plugins
 | 
				
			||||||
 | 
					*.exe
 | 
				
			||||||
 | 
					*.exe~
 | 
				
			||||||
 | 
					*.dll
 | 
				
			||||||
 | 
					*.so
 | 
				
			||||||
 | 
					*.dylib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Test binary, built with `go test -c`
 | 
				
			||||||
 | 
					*.test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Output of the go coverage tool, specifically when used with LiteIDE
 | 
				
			||||||
 | 
					*.out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Dependency directories (remove the comment below to include it)
 | 
				
			||||||
 | 
					# vendor/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Go workspace file
 | 
				
			||||||
 | 
					go.work
 | 
				
			||||||
 | 
					go.work.sum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# env file
 | 
				
			||||||
 | 
					.env
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					example/local_*
 | 
				
			||||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					MIT License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2025 Patial Tech
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					run:
 | 
				
			||||||
 | 
						go run ./cmd -o ./example/db ./example/schema.sql
 | 
				
			||||||
 | 
					bench-select:
 | 
				
			||||||
 | 
						go test ./example -bench BenchmarkSelect -memprofile memprofile.out -cpuprofile profile.out
 | 
				
			||||||
							
								
								
									
										5
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# pgm (Postgres Mapper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Simple query builder to work with Go:PG apps.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Will work along side with [dbmate](https://github.com/amacneil/dbmate), will consume schema.sql file created by dbmate
 | 
				
			||||||
							
								
								
									
										214
									
								
								cmd/generate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								cmd/generate.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"go/format"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"golang.org/x/text/cases"
 | 
				
			||||||
 | 
						"golang.org/x/text/language"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func generate(scheamPath, outDir string) error {
 | 
				
			||||||
 | 
						// read schame.sql
 | 
				
			||||||
 | 
						f, err := os.ReadFile(scheamPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse scheam.sql
 | 
				
			||||||
 | 
						tbls, err := parse(f)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Output dir, create if not exists.
 | 
				
			||||||
 | 
						if _, err := os.Stat(outDir); os.IsNotExist(err) {
 | 
				
			||||||
 | 
							if err := os.MkdirAll(outDir, 0740); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// schema.go will hold all tables info
 | 
				
			||||||
 | 
						var sb strings.Builder
 | 
				
			||||||
 | 
						sb.WriteString("// Code generated by code.patial.tech/go/pgm/cmd\n// DO NOT EDIT.\n\n")
 | 
				
			||||||
 | 
						sb.WriteString(fmt.Sprintf("package %s \n", filepath.Base(outDir)))
 | 
				
			||||||
 | 
						sb.WriteString(`
 | 
				
			||||||
 | 
						import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
					`)
 | 
				
			||||||
 | 
						caser := cases.Title(language.English)
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							modalDir   string
 | 
				
			||||||
 | 
							fieldCount int
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						for _, t := range tbls {
 | 
				
			||||||
 | 
							// skip schema_migrations
 | 
				
			||||||
 | 
							if t.Table == "schema_migrations" {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							name := pluralToSingular(t.Table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							name = strings.ReplaceAll(name, "_", " ")
 | 
				
			||||||
 | 
							name = caser.String(name)
 | 
				
			||||||
 | 
							name = strings.ReplaceAll(name, " ", "")
 | 
				
			||||||
 | 
							fieldCount = len(t.Columns)
 | 
				
			||||||
 | 
							sb.WriteString(fmt.Sprintf(
 | 
				
			||||||
 | 
								" %s = pgm.Table{Name: %q, FieldCount: %d", name, t.Table, fieldCount,
 | 
				
			||||||
 | 
							))
 | 
				
			||||||
 | 
							if len(t.PrimaryKey) > 0 {
 | 
				
			||||||
 | 
								sb.WriteString(", PK: []string{}")
 | 
				
			||||||
 | 
								sb.WriteString("}\n")
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								sb.WriteString("}\n")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modalDir = strings.ToLower(name)
 | 
				
			||||||
 | 
							os.Mkdir(filepath.Join(outDir, modalDir), 0740)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = writeColFile(t.Table, t.Columns, filepath.Join(outDir, modalDir), caser); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sb.WriteString(")")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Format code before saving
 | 
				
			||||||
 | 
						code, err := formatGoCode(sb.String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Save file to disk
 | 
				
			||||||
 | 
						os.WriteFile(filepath.Join(outDir, "schema.go"), code, 0640)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func writeColFile(tblName string, cols []*Column, outDir string, caser cases.Caser) error {
 | 
				
			||||||
 | 
						var sb strings.Builder
 | 
				
			||||||
 | 
						sb.WriteString("// Code generated by db-gen. DO NOT EDIT.\n\n")
 | 
				
			||||||
 | 
						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 (")
 | 
				
			||||||
 | 
						var name string
 | 
				
			||||||
 | 
						for _, c := range cols {
 | 
				
			||||||
 | 
							name = strings.ReplaceAll(c.Name, "_", " ")
 | 
				
			||||||
 | 
							name = caser.String(name)
 | 
				
			||||||
 | 
							name = strings.ReplaceAll(name, " ", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if strings.HasSuffix(name, "Id") {
 | 
				
			||||||
 | 
								name = name[0:len(name)-2] + "ID"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sb.WriteString(fmt.Sprintf("\n // %s field has db type %q", name, c.Type))
 | 
				
			||||||
 | 
							sb.WriteString(fmt.Sprintf("\n %s pgm.Field = %q", name, tblName+"."+c.Name))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sb.WriteString("\n)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Format code before saving
 | 
				
			||||||
 | 
						code, err := formatGoCode(sb.String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Save file to disk.
 | 
				
			||||||
 | 
						return os.WriteFile(filepath.Join(outDir, tblName+".go"), code, 0640)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// pluralToSingular converts plural table names to singular forms
 | 
				
			||||||
 | 
					func pluralToSingular(plural string) string {
 | 
				
			||||||
 | 
						// Handle special irregular plurals
 | 
				
			||||||
 | 
						irregulars := map[string]string{
 | 
				
			||||||
 | 
							"people":    "person",
 | 
				
			||||||
 | 
							"children":  "child",
 | 
				
			||||||
 | 
							"men":       "man",
 | 
				
			||||||
 | 
							"women":     "woman",
 | 
				
			||||||
 | 
							"feet":      "foot",
 | 
				
			||||||
 | 
							"teeth":     "tooth",
 | 
				
			||||||
 | 
							"mice":      "mouse",
 | 
				
			||||||
 | 
							"geese":     "goose",
 | 
				
			||||||
 | 
							"oxen":      "ox",
 | 
				
			||||||
 | 
							"data":      "datum",
 | 
				
			||||||
 | 
							"criteria":  "criterion",
 | 
				
			||||||
 | 
							"phenomena": "phenomenon",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lower := strings.ToLower(plural)
 | 
				
			||||||
 | 
						if singular, exists := irregulars[lower]; exists {
 | 
				
			||||||
 | 
							return singular
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -ies (cities -> city, companies -> company)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "ies") && len(plural) > 3 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-3] + "y"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -ves (lives -> life, wives -> wife)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "ves") && len(plural) > 3 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-3] + "fe"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -ses (classes -> class, addresses -> address)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "ses") && len(plural) > 3 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-2]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -xes (boxes -> box, taxes -> tax)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "xes") && len(plural) > 3 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-2]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -ches (watches -> watch, benches -> bench)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "ches") && len(plural) > 4 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-2]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -shes (dishes -> dish, bushes -> bush)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "shes") && len(plural) > 4 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-2]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle words ending in -oes (heroes -> hero, potatoes -> potato)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "oes") && len(plural) > 3 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-2]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle general -es endings
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "es") && len(plural) > 2 {
 | 
				
			||||||
 | 
							withoutEs := plural[:len(plural)-2]
 | 
				
			||||||
 | 
							if len(withoutEs) > 0 {
 | 
				
			||||||
 | 
								lastChar := strings.ToLower(string(withoutEs[len(withoutEs)-1]))
 | 
				
			||||||
 | 
								// Only remove -es (not just -s) if the word ends in s, x, ch, sh, o (those need the -es)
 | 
				
			||||||
 | 
								if lastChar == "s" || lastChar == "x" || lastChar == "o" ||
 | 
				
			||||||
 | 
									strings.HasSuffix(strings.ToLower(withoutEs), "ch") ||
 | 
				
			||||||
 | 
									strings.HasSuffix(strings.ToLower(withoutEs), "sh") {
 | 
				
			||||||
 | 
									return withoutEs
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// For words like "references", "preferences" - just remove the -s
 | 
				
			||||||
 | 
							return plural[:len(plural)-1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle simple -s endings (users -> user, books -> book)
 | 
				
			||||||
 | 
						if strings.HasSuffix(lower, "s") && len(plural) > 1 {
 | 
				
			||||||
 | 
							return plural[:len(plural)-1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If no rules match, return as-is (might already be singular)
 | 
				
			||||||
 | 
						return plural
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// formatGoCode formats the input Go code using go/format.
 | 
				
			||||||
 | 
					func formatGoCode(str string) ([]byte, error) {
 | 
				
			||||||
 | 
						formatted, err := format.Source([]byte(str))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("formatting code: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return formatted, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								cmd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								cmd/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const usageTxt = `Please provide output director and input schema.
 | 
				
			||||||
 | 
					Example:
 | 
				
			||||||
 | 
					  pgm/cmd -o ./db ./db/schema.sql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						var outDir string
 | 
				
			||||||
 | 
						flag.StringVar(&outDir, "o", "", "-o as output directory path")
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
						if len(os.Args) < 4 {
 | 
				
			||||||
 | 
							fmt.Print(usageTxt)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if outDir == "" {
 | 
				
			||||||
 | 
							println("missing, -o output directory path")
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := generate(os.Args[3], outDir); err != nil {
 | 
				
			||||||
 | 
							println(err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										139
									
								
								cmd/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								cmd/parse.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type (
 | 
				
			||||||
 | 
						TableInfo struct {
 | 
				
			||||||
 | 
							Schema     string
 | 
				
			||||||
 | 
							Table      string
 | 
				
			||||||
 | 
							Columns    []*Column
 | 
				
			||||||
 | 
							PrimaryKey []string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Column struct {
 | 
				
			||||||
 | 
							// Name of column
 | 
				
			||||||
 | 
							Name string
 | 
				
			||||||
 | 
							// Type of column
 | 
				
			||||||
 | 
							Type string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parse(scheam []byte) ([]*TableInfo, error) {
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							t      = false
 | 
				
			||||||
 | 
							n      = bytes.Count(scheam, []byte("CREATE TABLE"))
 | 
				
			||||||
 | 
							tables = make([]*TableInfo, n)
 | 
				
			||||||
 | 
							i      = 0
 | 
				
			||||||
 | 
							sb     strings.Builder
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for l := range bytes.SplitSeq(scheam, []byte("\n")) {
 | 
				
			||||||
 | 
							if strings.HasPrefix(string(l), "CREATE TABLE") {
 | 
				
			||||||
 | 
								t = true
 | 
				
			||||||
 | 
								sb.Write(l)
 | 
				
			||||||
 | 
							} else if t {
 | 
				
			||||||
 | 
								sb.Write(l)
 | 
				
			||||||
 | 
								if strings.Contains(string(l), ";") {
 | 
				
			||||||
 | 
									t = false
 | 
				
			||||||
 | 
									t, err := parseTableStmt(sb.String())
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tables[i] = t
 | 
				
			||||||
 | 
									i++
 | 
				
			||||||
 | 
									sb.Reset()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tables, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseTableStmt(sqlStatement string) (*TableInfo, error) {
 | 
				
			||||||
 | 
						// Regex to match CREATE TABLE statement and extract table name and column definitions
 | 
				
			||||||
 | 
						tableRegex := regexp.MustCompile(`(?i)CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:` + "`" + `?(\w+)` + "`" + `?\.)?` + "`" + `?(\w+)` + "`" + `?\s*\(([\s\S]*?)\)(?:\s*ENGINE.*?)?;`)
 | 
				
			||||||
 | 
						matches := tableRegex.FindStringSubmatch(sqlStatement)
 | 
				
			||||||
 | 
						if matches == nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("no CREATE TABLE statement found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						schema := matches[1]
 | 
				
			||||||
 | 
						tbl := matches[2]
 | 
				
			||||||
 | 
						cols := matches[3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse column definitions by splitting first on commas
 | 
				
			||||||
 | 
						// This is a simplistic approach - a real SQL parser would be more robust
 | 
				
			||||||
 | 
						columns := parseColumns(cols)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &TableInfo{
 | 
				
			||||||
 | 
							Schema:  schema,
 | 
				
			||||||
 | 
							Table:   tbl,
 | 
				
			||||||
 | 
							Columns: columns,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseColumns(colsStr string) []*Column {
 | 
				
			||||||
 | 
						var columns []*Column
 | 
				
			||||||
 | 
						var currentDef strings.Builder
 | 
				
			||||||
 | 
						parenthesesCount := 0
 | 
				
			||||||
 | 
						inQuote := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// First, intelligently split the column definitions
 | 
				
			||||||
 | 
						var columnDefs []string
 | 
				
			||||||
 | 
						for _, char := range colsStr {
 | 
				
			||||||
 | 
							if char == '(' {
 | 
				
			||||||
 | 
								parenthesesCount++
 | 
				
			||||||
 | 
								currentDef.WriteRune(char)
 | 
				
			||||||
 | 
							} else if char == ')' {
 | 
				
			||||||
 | 
								parenthesesCount--
 | 
				
			||||||
 | 
								currentDef.WriteRune(char)
 | 
				
			||||||
 | 
							} else if char == '\'' || char == '"' {
 | 
				
			||||||
 | 
								inQuote = !inQuote
 | 
				
			||||||
 | 
								currentDef.WriteRune(char)
 | 
				
			||||||
 | 
							} else if char == ',' && parenthesesCount == 0 && !inQuote {
 | 
				
			||||||
 | 
								// End of a column definition
 | 
				
			||||||
 | 
								columnDefs = append(columnDefs, strings.TrimSpace(currentDef.String()))
 | 
				
			||||||
 | 
								currentDef.Reset()
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								currentDef.WriteRune(char)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add the last column definition if there's anything left
 | 
				
			||||||
 | 
						if currentDef.Len() > 0 {
 | 
				
			||||||
 | 
							columnDefs = append(columnDefs, strings.TrimSpace(currentDef.String()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now parse each column definition
 | 
				
			||||||
 | 
						for _, colDef := range columnDefs {
 | 
				
			||||||
 | 
							// Skip constraints and keys that don't define columns
 | 
				
			||||||
 | 
							if strings.HasPrefix(strings.ToUpper(colDef), "PRIMARY KEY") ||
 | 
				
			||||||
 | 
								strings.HasPrefix(strings.ToUpper(colDef), "UNIQUE KEY") ||
 | 
				
			||||||
 | 
								strings.HasPrefix(strings.ToUpper(colDef), "FOREIGN KEY") ||
 | 
				
			||||||
 | 
								strings.HasPrefix(strings.ToUpper(colDef), "KEY") ||
 | 
				
			||||||
 | 
								strings.HasPrefix(strings.ToUpper(colDef), "INDEX") ||
 | 
				
			||||||
 | 
								strings.HasPrefix(strings.ToUpper(colDef), "CONSTRAINT") {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							colNameRegex := regexp.MustCompile(`^` + "`" + `?(\w+)` + "`" + `?\s+(.+)$`)
 | 
				
			||||||
 | 
							matches := colNameRegex.FindStringSubmatch(colDef)
 | 
				
			||||||
 | 
							if matches != nil {
 | 
				
			||||||
 | 
								columns = append(columns, &Column{
 | 
				
			||||||
 | 
									Name: matches[1],
 | 
				
			||||||
 | 
									Type: strings.TrimSpace(matches[2]),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return columns
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								example/db/branchuser/branch_users.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/db/branchuser/branch_users.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					// Code generated by db-gen. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package branchuser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// BranchID field has db type "bigint NOT NULL"
 | 
				
			||||||
 | 
						BranchID pgm.Field = "branch_users.branch_id"
 | 
				
			||||||
 | 
						// UserID field has db type "bigint NOT NULL"
 | 
				
			||||||
 | 
						UserID pgm.Field = "branch_users.user_id"
 | 
				
			||||||
 | 
						// RoleID field has db type "smallint NOT NULL"
 | 
				
			||||||
 | 
						RoleID pgm.Field = "branch_users.role_id"
 | 
				
			||||||
 | 
						// CreatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"
 | 
				
			||||||
 | 
						CreatedAt pgm.Field = "branch_users.created_at"
 | 
				
			||||||
 | 
						// UpdatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"
 | 
				
			||||||
 | 
						UpdatedAt pgm.Field = "branch_users.updated_at"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										18
									
								
								example/db/comment/comments.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/db/comment/comments.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					// Code generated by db-gen. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// ID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						ID pgm.Field = "comments.id"
 | 
				
			||||||
 | 
						// PostID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						PostID pgm.Field = "comments.post_id"
 | 
				
			||||||
 | 
						// UserID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						UserID pgm.Field = "comments.user_id"
 | 
				
			||||||
 | 
						// Content field has db type "text NOT NULL"
 | 
				
			||||||
 | 
						Content pgm.Field = "comments.content"
 | 
				
			||||||
 | 
						// CreatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
				
			||||||
 | 
						CreatedAt pgm.Field = "comments.created_at"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										18
									
								
								example/db/employee/employees.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/db/employee/employees.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					// Code generated by db-gen. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package employee
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// ID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						ID pgm.Field = "employees.id"
 | 
				
			||||||
 | 
						// Name field has db type "var NOT NULL"
 | 
				
			||||||
 | 
						Name pgm.Field = "employees.name"
 | 
				
			||||||
 | 
						// Department field has db type "var NOT NULL"
 | 
				
			||||||
 | 
						Department pgm.Field = "employees.department"
 | 
				
			||||||
 | 
						// Salary field has db type "decimal(10,2)"
 | 
				
			||||||
 | 
						Salary pgm.Field = "employees.salary"
 | 
				
			||||||
 | 
						// CreatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP    updated_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
				
			||||||
 | 
						CreatedAt pgm.Field = "employees.created_at"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										18
									
								
								example/db/post/posts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/db/post/posts.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					// Code generated by db-gen. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package post
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// ID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						ID pgm.Field = "posts.id"
 | 
				
			||||||
 | 
						// UserID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						UserID pgm.Field = "posts.user_id"
 | 
				
			||||||
 | 
						// Title field has db type "character varying(255) NOT NULL"
 | 
				
			||||||
 | 
						Title pgm.Field = "posts.title"
 | 
				
			||||||
 | 
						// Content field has db type "text"
 | 
				
			||||||
 | 
						Content pgm.Field = "posts.content"
 | 
				
			||||||
 | 
						// CreatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
				
			||||||
 | 
						CreatedAt pgm.Field = "posts.created_at"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										15
									
								
								example/db/schema.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								example/db/schema.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					// Code generated by code.patial.tech/go/pgm/cmd
 | 
				
			||||||
 | 
					// DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						User        = pgm.Table{Name: "users", FieldCount: 11}
 | 
				
			||||||
 | 
						UserSession = pgm.Table{Name: "user_sessions", FieldCount: 8}
 | 
				
			||||||
 | 
						BranchUser  = pgm.Table{Name: "branch_users", FieldCount: 5}
 | 
				
			||||||
 | 
						Post        = pgm.Table{Name: "posts", FieldCount: 5}
 | 
				
			||||||
 | 
						Comment     = pgm.Table{Name: "comments", FieldCount: 5}
 | 
				
			||||||
 | 
						Employee    = pgm.Table{Name: "employees", FieldCount: 5}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										30
									
								
								example/db/user/users.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								example/db/user/users.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					// Code generated by db-gen. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// ID field has db type "integer NOT NULL"
 | 
				
			||||||
 | 
						ID pgm.Field = "users.id"
 | 
				
			||||||
 | 
						// Name field has db type "character varying(255) NOT NULL"
 | 
				
			||||||
 | 
						Name pgm.Field = "users.name"
 | 
				
			||||||
 | 
						// Email field has db type "character varying(255) NOT NULL"
 | 
				
			||||||
 | 
						Email pgm.Field = "users.email"
 | 
				
			||||||
 | 
						// Phone field has db type "character varying(20)"
 | 
				
			||||||
 | 
						Phone pgm.Field = "users.phone"
 | 
				
			||||||
 | 
						// FirstName field has db type "character varying(50) NOT NULL"
 | 
				
			||||||
 | 
						FirstName pgm.Field = "users.first_name"
 | 
				
			||||||
 | 
						// MiddleName field has db type "character varying(50)"
 | 
				
			||||||
 | 
						MiddleName pgm.Field = "users.middle_name"
 | 
				
			||||||
 | 
						// LastName field has db type "character varying(50) NOT NULL"
 | 
				
			||||||
 | 
						LastName pgm.Field = "users.last_name"
 | 
				
			||||||
 | 
						// StatusID field has db type "smallint"
 | 
				
			||||||
 | 
						StatusID pgm.Field = "users.status_id"
 | 
				
			||||||
 | 
						// MfaKind field has db type "character varying(50) DEFAULT 'None'::character varying"
 | 
				
			||||||
 | 
						MfaKind pgm.Field = "users.mfa_kind"
 | 
				
			||||||
 | 
						// CreatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
				
			||||||
 | 
						CreatedAt pgm.Field = "users.created_at"
 | 
				
			||||||
 | 
						// UpdatedAt field has db type "timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP"
 | 
				
			||||||
 | 
						UpdatedAt pgm.Field = "users.updated_at"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										24
									
								
								example/db/usersession/user_sessions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								example/db/usersession/user_sessions.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					// Code generated by db-gen. DO NOT EDIT.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package usersession
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// 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"
 | 
				
			||||||
 | 
						CreatedAt pgm.Field = "user_sessions.created_at"
 | 
				
			||||||
 | 
						// UpdatedAt field has db type "timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL"
 | 
				
			||||||
 | 
						UpdatedAt pgm.Field = "user_sessions.updated_at"
 | 
				
			||||||
 | 
						// ExpiresAt field has db type "timestamp with time zone NOT NULL"
 | 
				
			||||||
 | 
						ExpiresAt pgm.Field = "user_sessions.expires_at"
 | 
				
			||||||
 | 
						// BranchID field has db type "bigint"
 | 
				
			||||||
 | 
						BranchID pgm.Field = "user_sessions.branch_id"
 | 
				
			||||||
 | 
						// UserID field has db type "bigint NOT NULL"
 | 
				
			||||||
 | 
						UserID pgm.Field = "user_sessions.user_id"
 | 
				
			||||||
 | 
						// RoleID field has db type "smallint"
 | 
				
			||||||
 | 
						RoleID pgm.Field = "user_sessions.role_id"
 | 
				
			||||||
 | 
						// LoginIp field has db type "character varying NOT NULL"
 | 
				
			||||||
 | 
						LoginIp pgm.Field = "user_sessions.login_ip"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										3
									
								
								example/generate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								example/generate.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					//go:generate go run code.patial.tech/go/pgm/cmd -o ./db ./schema.sql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package example
 | 
				
			||||||
							
								
								
									
										18
									
								
								example/qry_delete_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/qry_delete_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					package example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/user"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDelete(t *testing.T) {
 | 
				
			||||||
 | 
						expected := "DELETE FROM users WHERE users.id = $1 AND users.status_id NOT IN($2)"
 | 
				
			||||||
 | 
						got := db.User.Delete().
 | 
				
			||||||
 | 
							Where(user.ID.Eq(1), user.StatusID.NotIn(1, 2, 3)).
 | 
				
			||||||
 | 
							String()
 | 
				
			||||||
 | 
						if got != expected {
 | 
				
			||||||
 | 
							t.Errorf("got %q, want %q", got, expected)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										84
									
								
								example/qry_insert_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								example/qry_insert_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					package example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/user"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestInsertQuery(t *testing.T) {
 | 
				
			||||||
 | 
						got := db.User.Insert().
 | 
				
			||||||
 | 
							Set(user.Email, "aa@aa.com").
 | 
				
			||||||
 | 
							Set(user.Phone, 8889991234).
 | 
				
			||||||
 | 
							Set(user.FirstName, "fname").
 | 
				
			||||||
 | 
							Set(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 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").
 | 
				
			||||||
 | 
							Set(user.Phone, 8889991234).
 | 
				
			||||||
 | 
							OnConflict(user.ID).
 | 
				
			||||||
 | 
							DoNothing().
 | 
				
			||||||
 | 
							String()
 | 
				
			||||||
 | 
						expected := "INSERT INTO users(email, phone) VALUES($1, $2) ON CONFLICT (id) DO NOTHING"
 | 
				
			||||||
 | 
						if got != expected {
 | 
				
			||||||
 | 
							t.Errorf("\nexpected: %q\ngot: %q", expected, got)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BenchmarkInsertQuery-12    	 1952412	       605.3 ns/op	    1144 B/op	      18 allocs/op
 | 
				
			||||||
 | 
					func BenchmarkInsertQuery(b *testing.B) {
 | 
				
			||||||
 | 
						for b.Loop() {
 | 
				
			||||||
 | 
							_ = db.User.Insert().
 | 
				
			||||||
 | 
								Set(user.Email, "aa@aa.com").
 | 
				
			||||||
 | 
								Set(user.Phone, 8889991234).
 | 
				
			||||||
 | 
								Set(user.FirstName, "fname").
 | 
				
			||||||
 | 
								Set(user.LastName, "lname").
 | 
				
			||||||
 | 
								Returning(user.ID).
 | 
				
			||||||
 | 
								String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BenchmarkInsertSetMap-12    	 1534039	       777.1 ns/op	    1480 B/op	      20 allocs/op
 | 
				
			||||||
 | 
					func BenchmarkInsertSetMap(b *testing.B) {
 | 
				
			||||||
 | 
						for b.Loop() {
 | 
				
			||||||
 | 
							_ = 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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								example/qry_select_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								example/qry_select_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					package example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/branchuser"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/employee"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/user"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/usersession"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestQryBuilder2(t *testing.T) {
 | 
				
			||||||
 | 
						got := db.User.Debug().Select(user.Email, user.FirstName).
 | 
				
			||||||
 | 
							Join(db.UserSession, user.ID, usersession.UserID).
 | 
				
			||||||
 | 
							Join(db.BranchUser, user.ID, branchuser.UserID).
 | 
				
			||||||
 | 
							Where(
 | 
				
			||||||
 | 
								user.ID.Eq(1),
 | 
				
			||||||
 | 
								pgm.Or(
 | 
				
			||||||
 | 
									user.StatusID.Eq(2),
 | 
				
			||||||
 | 
									user.UpdatedAt.Eq(3),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
								user.MfaKind.Eq(4),
 | 
				
			||||||
 | 
								pgm.Or(
 | 
				
			||||||
 | 
									user.FirstName.Eq(5),
 | 
				
			||||||
 | 
									user.MiddleName.Eq(6),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Where(
 | 
				
			||||||
 | 
								user.LastName.NEq(7),
 | 
				
			||||||
 | 
								user.Phone.Like("%123%"),
 | 
				
			||||||
 | 
								user.Email.NotInSubQuery(db.User.Select(user.ID).Where(user.ID.Eq(123))),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Limit(10).
 | 
				
			||||||
 | 
							Offset(100).
 | 
				
			||||||
 | 
							String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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.email NOT IN(SELECT users.id FROM users WHERE users.id = $9) LIMIT 10 OFFSET 100"
 | 
				
			||||||
 | 
						if expected != got {
 | 
				
			||||||
 | 
							t.Errorf("\nexpected: %q\ngot: %q", expected, got)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSelectWithHaving(t *testing.T) {
 | 
				
			||||||
 | 
						expected := "SELECT employees.department, AVG(employees.salary), COUNT(employees.id)" +
 | 
				
			||||||
 | 
							" FROM employees GROUP BY employees.department HAVING AVG(employees.salary) > $1 AND COUNT(employees.id) > $2"
 | 
				
			||||||
 | 
						got := db.Employee.
 | 
				
			||||||
 | 
							Select(employee.Department, employee.Salary.Avg(), employee.ID.Count()).
 | 
				
			||||||
 | 
							GroupBy(employee.Department).
 | 
				
			||||||
 | 
							Having(employee.Salary.Avg().Gt(50000), employee.ID.Count().Gt(5)).
 | 
				
			||||||
 | 
							String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if expected != got {
 | 
				
			||||||
 | 
							t.Errorf("\nexpected: %q\ngot: %q", expected, got)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BenchmarkSelect-12    	  668817	      1753 ns/op	    4442 B/op	      59 allocs/op
 | 
				
			||||||
 | 
					// BenchmarkSelect-12    	  638901	      1860 ns/op	    4266 B/op	      61 allocs/op
 | 
				
			||||||
 | 
					func BenchmarkSelect(b *testing.B) {
 | 
				
			||||||
 | 
						for b.Loop() {
 | 
				
			||||||
 | 
							_ = db.User.Select(user.Email, user.FirstName).
 | 
				
			||||||
 | 
								Join(db.UserSession, user.ID, usersession.UserID).
 | 
				
			||||||
 | 
								Join(db.BranchUser, user.ID, branchuser.UserID).
 | 
				
			||||||
 | 
								Where(
 | 
				
			||||||
 | 
									user.ID.Eq(1),
 | 
				
			||||||
 | 
									pgm.Or(
 | 
				
			||||||
 | 
										user.StatusID.Eq(2),
 | 
				
			||||||
 | 
										user.UpdatedAt.Eq(3),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									user.MfaKind.Eq(4),
 | 
				
			||||||
 | 
									pgm.Or(
 | 
				
			||||||
 | 
										user.FirstName.Eq(5),
 | 
				
			||||||
 | 
										user.MiddleName.Eq(6),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								).
 | 
				
			||||||
 | 
								Where(
 | 
				
			||||||
 | 
									user.LastName.NEq(7),
 | 
				
			||||||
 | 
									user.Phone.Like("%123%"),
 | 
				
			||||||
 | 
									user.Email.NotInSubQuery(db.User.Select(user.ID).Where(user.ID.Eq(123))),
 | 
				
			||||||
 | 
								).
 | 
				
			||||||
 | 
								Limit(10).
 | 
				
			||||||
 | 
								Offset(100).
 | 
				
			||||||
 | 
								String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										66
									
								
								example/qry_update_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								example/qry_update_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					package example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db"
 | 
				
			||||||
 | 
						"code.patial.tech/go/pgm/example/db/user"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestUpdateQuery(t *testing.T) {
 | 
				
			||||||
 | 
						got := db.User.Update().
 | 
				
			||||||
 | 
							Set(user.FirstName, "ankit").
 | 
				
			||||||
 | 
							Set(user.MiddleName, "singh").
 | 
				
			||||||
 | 
							Set(user.LastName, "patial").
 | 
				
			||||||
 | 
							Where(
 | 
				
			||||||
 | 
								user.Email.Eq("aa@aa.com"),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Where(
 | 
				
			||||||
 | 
								user.StatusID.NEq(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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected := "UPDATE users SET first_name=$1, middle_name=$2, last_name=$3 WHERE users.email = $4 AND users.status_id != $5"
 | 
				
			||||||
 | 
						if got != expected {
 | 
				
			||||||
 | 
							t.Errorf("\nexpected: %q\ngot: %q", expected, got)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BenchmarkUpdateQuery-12    	 2004985	       592.2 ns/op	    1176 B/op	      20 allocs/op
 | 
				
			||||||
 | 
					func BenchmarkUpdateQuery(b *testing.B) {
 | 
				
			||||||
 | 
						for b.Loop() {
 | 
				
			||||||
 | 
							_ = db.User.Update().
 | 
				
			||||||
 | 
								Set(user.FirstName, "ankit").
 | 
				
			||||||
 | 
								Set(user.MiddleName, "singh").
 | 
				
			||||||
 | 
								Set(user.LastName, "patial").
 | 
				
			||||||
 | 
								Where(
 | 
				
			||||||
 | 
									user.Email.Eq("aa@aa.com"),
 | 
				
			||||||
 | 
								).
 | 
				
			||||||
 | 
								Where(
 | 
				
			||||||
 | 
									user.StatusID.NEq(1),
 | 
				
			||||||
 | 
								).
 | 
				
			||||||
 | 
								String()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										306
									
								
								example/schema.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								example/schema.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
				
			|||||||
 | 
					--
 | 
				
			||||||
 | 
					-- PostgreSQL database dump
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Dumped from database version 15.3
 | 
				
			||||||
 | 
					-- Dumped by pg_dump version 15.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SET statement_timeout = 0;
 | 
				
			||||||
 | 
					SET lock_timeout = 0;
 | 
				
			||||||
 | 
					SET idle_in_transaction_session_timeout = 0;
 | 
				
			||||||
 | 
					SET client_encoding = 'UTF8';
 | 
				
			||||||
 | 
					SET standard_conforming_strings = on;
 | 
				
			||||||
 | 
					SELECT pg_catalog.set_config('search_path', '', false);
 | 
				
			||||||
 | 
					SET check_function_bodies = false;
 | 
				
			||||||
 | 
					SET xmloption = content;
 | 
				
			||||||
 | 
					SET client_min_messages = warning;
 | 
				
			||||||
 | 
					SET row_security = off;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: myapp_development; Type: DATABASE; Schema: -; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE DATABASE myapp_development WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE = 'en_US.UTF-8';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER DATABASE myapp_development OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					\connect myapp_development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SET statement_timeout = 0;
 | 
				
			||||||
 | 
					SET lock_timeout = 0;
 | 
				
			||||||
 | 
					SET idle_in_transaction_session_timeout = 0;
 | 
				
			||||||
 | 
					SET client_encoding = 'UTF8';
 | 
				
			||||||
 | 
					SET standard_conforming_strings = on;
 | 
				
			||||||
 | 
					SELECT pg_catalog.set_config('search_path', '', false);
 | 
				
			||||||
 | 
					SET check_function_bodies = false;
 | 
				
			||||||
 | 
					SET xmloption = content;
 | 
				
			||||||
 | 
					SET client_min_messages = warning;
 | 
				
			||||||
 | 
					SET row_security = off;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.schema_migrations (
 | 
				
			||||||
 | 
					    version character varying(255) NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.schema_migrations OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users; Type: TABLE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.users (
 | 
				
			||||||
 | 
					    id integer NOT NULL,
 | 
				
			||||||
 | 
					    name character varying(255) NOT NULL,
 | 
				
			||||||
 | 
					    email character varying(255) NOT NULL,
 | 
				
			||||||
 | 
					    phone character varying(20),
 | 
				
			||||||
 | 
					    first_name character varying(50) NOT NULL,
 | 
				
			||||||
 | 
					    middle_name character varying(50),
 | 
				
			||||||
 | 
					    last_name character varying(50) NOT NULL,
 | 
				
			||||||
 | 
					    status_id smallint,
 | 
				
			||||||
 | 
					    mfa_kind character varying(50) DEFAULT 'None'::character varying,
 | 
				
			||||||
 | 
					    created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
				
			||||||
 | 
					    updated_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.users OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.user_sessions (
 | 
				
			||||||
 | 
					    id character varying NOT NULL,
 | 
				
			||||||
 | 
					    created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
 | 
				
			||||||
 | 
					    updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
 | 
				
			||||||
 | 
					    expires_at timestamp with time zone NOT NULL,
 | 
				
			||||||
 | 
					    branch_id bigint,
 | 
				
			||||||
 | 
					    user_id bigint NOT NULL,
 | 
				
			||||||
 | 
					    role_id smallint,
 | 
				
			||||||
 | 
					    login_ip character varying NOT NULL,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.branch_users (
 | 
				
			||||||
 | 
					    branch_id bigint NOT NULL,
 | 
				
			||||||
 | 
					    user_id bigint NOT NULL,
 | 
				
			||||||
 | 
					    role_id smallint NOT NULL,
 | 
				
			||||||
 | 
					    created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
 | 
				
			||||||
 | 
					    updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE SEQUENCE public.users_id_seq
 | 
				
			||||||
 | 
					    AS integer
 | 
				
			||||||
 | 
					    START WITH 1
 | 
				
			||||||
 | 
					    INCREMENT BY 1
 | 
				
			||||||
 | 
					    NO MINVALUE
 | 
				
			||||||
 | 
					    NO MAXVALUE
 | 
				
			||||||
 | 
					    CACHE 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.users_id_seq OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: posts; Type: TABLE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.posts (
 | 
				
			||||||
 | 
					    id integer NOT NULL,
 | 
				
			||||||
 | 
					    user_id integer NOT NULL,
 | 
				
			||||||
 | 
					    title character varying(255) NOT NULL,
 | 
				
			||||||
 | 
					    content text,
 | 
				
			||||||
 | 
					    created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.posts OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: posts_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE SEQUENCE public.posts_id_seq
 | 
				
			||||||
 | 
					    AS integer
 | 
				
			||||||
 | 
					    START WITH 1
 | 
				
			||||||
 | 
					    INCREMENT BY 1
 | 
				
			||||||
 | 
					    NO MINVALUE
 | 
				
			||||||
 | 
					    NO MAXVALUE
 | 
				
			||||||
 | 
					    CACHE 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.posts_id_seq OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: posts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER SEQUENCE public.posts_id_seq OWNED BY public.posts.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments; Type: TABLE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.comments (
 | 
				
			||||||
 | 
					    id integer NOT NULL,
 | 
				
			||||||
 | 
					    post_id integer NOT NULL,
 | 
				
			||||||
 | 
					    user_id integer NOT NULL,
 | 
				
			||||||
 | 
					    content text NOT NULL,
 | 
				
			||||||
 | 
					    created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.comments OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE TABLE public.employees (
 | 
				
			||||||
 | 
					    id integer NOT NULL,
 | 
				
			||||||
 | 
					    name var NOT NULL,
 | 
				
			||||||
 | 
					    department var NOT NULL,
 | 
				
			||||||
 | 
					    salary decimal(10,2) ,
 | 
				
			||||||
 | 
					    created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
 | 
				
			||||||
 | 
					    updated_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE SEQUENCE public.comments_id_seq
 | 
				
			||||||
 | 
					    AS integer
 | 
				
			||||||
 | 
					    START WITH 1
 | 
				
			||||||
 | 
					    INCREMENT BY 1
 | 
				
			||||||
 | 
					    NO MINVALUE
 | 
				
			||||||
 | 
					    NO MAXVALUE
 | 
				
			||||||
 | 
					    CACHE 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE public.comments_id_seq OWNER TO postgres;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER SEQUENCE public.comments_id_seq OWNED BY public.comments.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users id; Type: DEFAULT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: posts id; Type: DEFAULT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.posts ALTER COLUMN id SET DEFAULT nextval('public.posts_id_seq'::regclass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments id; Type: DEFAULT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.comments ALTER COLUMN id SET DEFAULT nextval('public.comments_id_seq'::regclass);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.schema_migrations
 | 
				
			||||||
 | 
					    ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.users
 | 
				
			||||||
 | 
					    ADD CONSTRAINT users_email_key UNIQUE (email);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.users
 | 
				
			||||||
 | 
					    ADD CONSTRAINT users_pkey PRIMARY KEY (id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: posts posts_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.posts
 | 
				
			||||||
 | 
					    ADD CONSTRAINT posts_pkey PRIMARY KEY (id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.comments
 | 
				
			||||||
 | 
					    ADD CONSTRAINT comments_pkey PRIMARY KEY (id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: idx_comments_post_id; Type: INDEX; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE INDEX idx_comments_post_id ON public.comments USING btree (post_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: idx_comments_user_id; Type: INDEX; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE INDEX idx_comments_user_id ON public.comments USING btree (user_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: idx_posts_user_id; Type: INDEX; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE INDEX idx_posts_user_id ON public.posts USING btree (user_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: posts posts_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.posts
 | 
				
			||||||
 | 
					    ADD CONSTRAINT posts_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments comments_post_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.comments
 | 
				
			||||||
 | 
					    ADD CONSTRAINT comments_post_id_fkey FOREIGN KEY (post_id) REFERENCES public.posts(id) ON DELETE CASCADE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Name: comments comments_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE ONLY public.comments
 | 
				
			||||||
 | 
					    ADD CONSTRAINT comments_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Data for Name: schema_migrations; Type: TABLE DATA; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY public.schema_migrations (version) FROM stdin;
 | 
				
			||||||
 | 
					20200227231541
 | 
				
			||||||
 | 
					20200227231542
 | 
				
			||||||
 | 
					20200227231543
 | 
				
			||||||
 | 
					\.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY public.users (id, name, email, created_at) FROM stdin;
 | 
				
			||||||
 | 
					\.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Data for Name: posts; Type: TABLE DATA; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY public.posts (id, user_id, title, content, created_at) FROM stdin;
 | 
				
			||||||
 | 
					\.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Data for Name: comments; Type: TABLE DATA; Schema: public; Owner: postgres
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY public.comments (id, post_id, user_id, content, created_at)
 | 
				
			||||||
							
								
								
									
										21
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					module code.patial.tech/go/pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.24.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/jackc/pgx v3.6.2+incompatible
 | 
				
			||||||
 | 
						golang.org/x/text v0.27.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 | 
				
			||||||
 | 
						github.com/jackc/puddle/v2 v2.2.2 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sync v0.16.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/jackc/pgx/v5 v5.7.5
 | 
				
			||||||
 | 
						github.com/pkg/errors v0.9.1 // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.40.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										25
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 | 
				
			||||||
 | 
					github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 | 
				
			||||||
 | 
					github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
 | 
				
			||||||
 | 
					github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 | 
				
			||||||
 | 
					github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
 | 
				
			||||||
 | 
					github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
 | 
				
			||||||
 | 
					github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
 | 
				
			||||||
 | 
					github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
 | 
				
			||||||
 | 
					github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
 | 
				
			||||||
 | 
					github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
				
			||||||
 | 
					golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
 | 
				
			||||||
 | 
					golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
							
								
								
									
										134
									
								
								pgm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								pgm.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5/pgtype"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Field ==>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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() + ")")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Avg fn wrapping of field
 | 
				
			||||||
 | 
					func (f Field) Avg() Field {
 | 
				
			||||||
 | 
						return Field("AVG(" + f.String() + ")")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f Field) Eq(val any) Conditioner {
 | 
				
			||||||
 | 
						col := f.String()
 | 
				
			||||||
 | 
						return &Cond{Field: col, Val: val, op: " = $", len: len(col) + 5}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EqualFold will user LOWER() for comparision
 | 
				
			||||||
 | 
					func (f Field) EqFold(val any) 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) Gte(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
 | 
				
			||||||
 | 
					func PgTime(t time.Time) pgtype.Timestamptz {
 | 
				
			||||||
 | 
						return pgtype.Timestamptz{Time: t, Valid: true}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func PgTimeNow() pgtype.Timestamptz {
 | 
				
			||||||
 | 
						return pgtype.Timestamptz{Time: time.Now(), Valid: true}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsNotFound error check
 | 
				
			||||||
 | 
					func IsNotFound(err error) bool {
 | 
				
			||||||
 | 
						return errors.Is(err, pgx.ErrNoRows)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ConcatWs(sep string, fields ...Field) string {
 | 
				
			||||||
 | 
						return "concat_ws('" + sep + "'," + joinFileds(fields) + ")"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func StringAgg(exp, sep string) string {
 | 
				
			||||||
 | 
						return "string_agg(" + exp + ",'" + sep + "')"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func StringAggCast(exp, sep string) string {
 | 
				
			||||||
 | 
						return "string_agg(cast(" + exp + " as varchar),'" + sep + "')"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								pool.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								pool.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					// 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)
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 {
 | 
				
			||||||
 | 
						MaxConns        int32
 | 
				
			||||||
 | 
						MinConns        int32
 | 
				
			||||||
 | 
						MaxConnLifetime time.Duration
 | 
				
			||||||
 | 
						MaxConnIdleTime time.Duration
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Init(connString string, conf *Config) {
 | 
				
			||||||
 | 
						cfg, err := pgxpool.ParseConfig(connString)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if conf != nil {
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetPool() *pgxpool.Pool {
 | 
				
			||||||
 | 
						return poolPGX.Load()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										240
									
								
								qry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								qry.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type (
 | 
				
			||||||
 | 
						Clause interface {
 | 
				
			||||||
 | 
							Select(fields ...Field) SelectClause
 | 
				
			||||||
 | 
							// Insert() InsertSet
 | 
				
			||||||
 | 
							// Update() UpdateSet
 | 
				
			||||||
 | 
							// 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 {
 | 
				
			||||||
 | 
							Exec(ctx context.Context) error
 | 
				
			||||||
 | 
							ExecTx(ctx context.Context, tx pgx.Tx) error
 | 
				
			||||||
 | 
							Stringer
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Stringer interface {
 | 
				
			||||||
 | 
							String() string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RowScanner interface {
 | 
				
			||||||
 | 
							Scan(dest ...any) error
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Or(cond ...Conditioner) Conditioner {
 | 
				
			||||||
 | 
						return &CondGroup{op: " OR ", cond: cond}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (cv *Cond) Condition(args *[]any, argIdx int) string {
 | 
				
			||||||
 | 
						// 1. condition with subquery
 | 
				
			||||||
 | 
						if cv.action == CondActionSubQuery {
 | 
				
			||||||
 | 
							qStr, newArgs := cv.Val.(SelectClause).raw(*args)
 | 
				
			||||||
 | 
							*args = newArgs
 | 
				
			||||||
 | 
							return cv.Field + strings.Replace(cv.op, "$", qStr, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2. normal condition
 | 
				
			||||||
 | 
						*args = append(*args, cv.Val)
 | 
				
			||||||
 | 
						var op string
 | 
				
			||||||
 | 
						if strings.HasSuffix(cv.op, "$") {
 | 
				
			||||||
 | 
							op = cv.op + strconv.Itoa(argIdx+1)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							op = strings.Replace(cv.op, "$", "$"+strconv.Itoa(argIdx+1), 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cv.action == CondActionNeedToClose {
 | 
				
			||||||
 | 
							return cv.Field + op + ")"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cv.Field + op
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *CondGroup) Condition(args *[]any, argIdx int) string {
 | 
				
			||||||
 | 
						sb := getSB()
 | 
				
			||||||
 | 
						defer putSB(sb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sb.WriteString("(")
 | 
				
			||||||
 | 
						for i, cond := range c.cond {
 | 
				
			||||||
 | 
							if i == 0 {
 | 
				
			||||||
 | 
								sb.WriteString(cond.Condition(args, argIdx+i))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								sb.WriteString(c.op)
 | 
				
			||||||
 | 
								sb.WriteString(cond.Condition(args, argIdx+i))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sb.WriteString(")")
 | 
				
			||||||
 | 
						return sb.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										74
									
								
								qry_delete.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								qry_delete.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					package pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type (
 | 
				
			||||||
 | 
						deleteQry struct {
 | 
				
			||||||
 | 
							table     string
 | 
				
			||||||
 | 
							condition []Conditioner
 | 
				
			||||||
 | 
							args      []any
 | 
				
			||||||
 | 
							debug     bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *deleteQry) Exec(ctx context.Context) error {
 | 
				
			||||||
 | 
						_, err := poolPGX.Load().Exec(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *deleteQry) ExecTx(ctx context.Context, tx pgx.Tx) error {
 | 
				
			||||||
 | 
						_, err := tx.Exec(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *deleteQry) String() string {
 | 
				
			||||||
 | 
						sb := getSB()
 | 
				
			||||||
 | 
						defer putSB(sb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						n := len("DELETE FROM ") + len(q.table) + 20
 | 
				
			||||||
 | 
						sb.Grow(n)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// DELETE
 | 
				
			||||||
 | 
						sb.WriteString("DELETE FROM ")
 | 
				
			||||||
 | 
						sb.WriteString(q.table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// WHERE
 | 
				
			||||||
 | 
						if len(q.condition) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" WHERE ")
 | 
				
			||||||
 | 
							var argIdx int
 | 
				
			||||||
 | 
							for i, c := range q.condition {
 | 
				
			||||||
 | 
								argIdx = len(q.args)
 | 
				
			||||||
 | 
								if i > 0 {
 | 
				
			||||||
 | 
									sb.WriteString(" AND ")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								sb.WriteString(c.Condition(&q.args, argIdx))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sb.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										150
									
								
								qry_insert.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								qry_insert.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"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,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return qb
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) Set(field Field, val any) InsertClause {
 | 
				
			||||||
 | 
						q.fields = append(q.fields, field.Name())
 | 
				
			||||||
 | 
						q.vals = append(q.vals, "$"+strconv.Itoa(len(q.args)+1))
 | 
				
			||||||
 | 
						q.args = append(q.args, val)
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) SetMap(cols map[Field]any) InsertClause {
 | 
				
			||||||
 | 
						for k, v := range cols {
 | 
				
			||||||
 | 
							q.Set(k, v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) Returning(field Field) First {
 | 
				
			||||||
 | 
						col := field.Name()
 | 
				
			||||||
 | 
						q.returing = &col
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) OnConflict(fields ...Field) Do {
 | 
				
			||||||
 | 
						if len(fields) > 0 {
 | 
				
			||||||
 | 
							sb := getSB()
 | 
				
			||||||
 | 
							defer putSB(sb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i, f := range fields {
 | 
				
			||||||
 | 
								if i == 0 {
 | 
				
			||||||
 | 
									sb.WriteString(f.Name())
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									sb.WriteString(", " + f.Name())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c := sb.String()
 | 
				
			||||||
 | 
							q.onConflict = &c
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) DoNothing() Execute {
 | 
				
			||||||
 | 
						q.conflictAction = "DO NOTHING"
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) DoUpdate(fields ...Field) Execute {
 | 
				
			||||||
 | 
						var sb strings.Builder
 | 
				
			||||||
 | 
						for i, f := range fields {
 | 
				
			||||||
 | 
							col := f.Name()
 | 
				
			||||||
 | 
							if i == 0 {
 | 
				
			||||||
 | 
								fmt.Fprintf(&sb, "%s = EXCLUDED.%s", col, col)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								fmt.Fprintf(&sb, ", %s = EXCLUDED.%s", col, col)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						q.conflictAction = "DO UPDATE SET " + sb.String()
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) Exec(ctx context.Context) error {
 | 
				
			||||||
 | 
						_, err := poolPGX.Load().Exec(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) ExecTx(ctx context.Context, tx pgx.Tx) error {
 | 
				
			||||||
 | 
						_, err := tx.Exec(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) First(ctx context.Context, dest ...any) error {
 | 
				
			||||||
 | 
						return poolPGX.Load().QueryRow(ctx, q.String(), q.args...).Scan(dest...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *insertQry) FirstTx(ctx context.Context, tx pgx.Tx, dest ...any) error {
 | 
				
			||||||
 | 
						return tx.QueryRow(ctx, q.String(), q.args...).Scan(dest...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// build query string
 | 
				
			||||||
 | 
					func (q *insertQry) String() string {
 | 
				
			||||||
 | 
						sb := getSB()
 | 
				
			||||||
 | 
						defer putSB(sb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						n := 12 + len(q.table) + 10
 | 
				
			||||||
 | 
						for i, c := range q.fields {
 | 
				
			||||||
 | 
							n += len(c) + len(" =$,"+strconv.Itoa(i))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sb.Grow(n)
 | 
				
			||||||
 | 
						sb.WriteString("INSERT INTO ")
 | 
				
			||||||
 | 
						sb.WriteString(q.table)
 | 
				
			||||||
 | 
						sb.WriteString("(")
 | 
				
			||||||
 | 
						sb.WriteString(strings.Join(q.fields, ", "))
 | 
				
			||||||
 | 
						sb.WriteString(") VALUES(")
 | 
				
			||||||
 | 
						sb.WriteString(strings.Join(q.vals, ", "))
 | 
				
			||||||
 | 
						sb.WriteString(")")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if q.onConflict != nil {
 | 
				
			||||||
 | 
							sb.WriteString(" ON CONFLICT (" + *q.onConflict + ") " + q.conflictAction)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if q.returing != nil {
 | 
				
			||||||
 | 
							sb.WriteString(" RETURNING " + *q.returing)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sb.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										286
									
								
								qry_select.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								qry_select.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/jackc/pgx/v5"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type (
 | 
				
			||||||
 | 
						selectQry struct {
 | 
				
			||||||
 | 
							table   string
 | 
				
			||||||
 | 
							fields  []Field
 | 
				
			||||||
 | 
							args    []any
 | 
				
			||||||
 | 
							join    []string
 | 
				
			||||||
 | 
							where   []Conditioner
 | 
				
			||||||
 | 
							groupBy []Field
 | 
				
			||||||
 | 
							having  []Conditioner
 | 
				
			||||||
 | 
							orderBy []Field
 | 
				
			||||||
 | 
							limit   int
 | 
				
			||||||
 | 
							offset  int
 | 
				
			||||||
 | 
							debug   bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CondAction uint8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Cond struct {
 | 
				
			||||||
 | 
							Val    any
 | 
				
			||||||
 | 
							op     string
 | 
				
			||||||
 | 
							Field  string
 | 
				
			||||||
 | 
							len    int
 | 
				
			||||||
 | 
							action CondAction
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CondGroup struct {
 | 
				
			||||||
 | 
							op   string
 | 
				
			||||||
 | 
							cond []Conditioner
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Contdition actions
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						CondActionNothing CondAction = iota
 | 
				
			||||||
 | 
						CondActionNeedToClose
 | 
				
			||||||
 | 
						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) SelectClause {
 | 
				
			||||||
 | 
						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 {
 | 
				
			||||||
 | 
						q.join = append(q.join, "LEFT JOIN "+t.Name+" ON "+t1Field.String()+" = "+t2Field.String())
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) RightJoin(t Table, t1Field, t2Field Field) SelectClause {
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) CrossJoin(t Table) SelectClause {
 | 
				
			||||||
 | 
						q.join = append(q.join, "CROSS JOIN "+t.Name)
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) Where(cond ...Conditioner) AfterWhere {
 | 
				
			||||||
 | 
						q.where = append(q.where, cond...)
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) OrderBy(fields ...Field) AfterOrderBy {
 | 
				
			||||||
 | 
						q.orderBy = fields
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) GroupBy(fields ...Field) AfterGroupBy {
 | 
				
			||||||
 | 
						q.groupBy = fields
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) Having(cond ...Conditioner) AfterHaving {
 | 
				
			||||||
 | 
						q.having = append(q.having, cond...)
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) Limit(v int) AfterLimit {
 | 
				
			||||||
 | 
						q.limit = v
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) Offset(v int) AfterOffset {
 | 
				
			||||||
 | 
						q.offset = v
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) First(ctx context.Context, dest ...any) error {
 | 
				
			||||||
 | 
						return poolPGX.Load().QueryRow(ctx, q.String(), q.args...).Scan(dest...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) FirstTx(ctx context.Context, tx pgx.Tx, dest ...any) error {
 | 
				
			||||||
 | 
						return tx.QueryRow(ctx, q.String(), q.args...).Scan(dest...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) All(ctx context.Context, row RowsCb) error {
 | 
				
			||||||
 | 
						rows, err := poolPGX.Load().Query(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if errors.Is(err, pgx.ErrNoRows) {
 | 
				
			||||||
 | 
							return ErrNoRows
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer rows.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for rows.Next() {
 | 
				
			||||||
 | 
							if err := row(rows); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) AllTx(ctx context.Context, tx pgx.Tx, row RowsCb) error {
 | 
				
			||||||
 | 
						rows, err := tx.Query(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if errors.Is(err, pgx.ErrNoRows) {
 | 
				
			||||||
 | 
							return ErrNoRows
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer rows.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for rows.Next() {
 | 
				
			||||||
 | 
							if err := row(rows); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) raw(prefixArgs []any) (string, []any) {
 | 
				
			||||||
 | 
						q.args = append(prefixArgs, q.args...)
 | 
				
			||||||
 | 
						return q.String(), q.args
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) String() string {
 | 
				
			||||||
 | 
						sb := getSB()
 | 
				
			||||||
 | 
						defer putSB(sb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sb.Grow(q.averageLen())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// SELECT
 | 
				
			||||||
 | 
						sb.WriteString("SELECT ")
 | 
				
			||||||
 | 
						sb.WriteString(joinFileds(q.fields))
 | 
				
			||||||
 | 
						sb.WriteString(" FROM " + q.table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// JOIN
 | 
				
			||||||
 | 
						if len(q.join) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" " + strings.Join(q.join, " "))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// WHERE
 | 
				
			||||||
 | 
						if len(q.where) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" WHERE ")
 | 
				
			||||||
 | 
							var argIdx int
 | 
				
			||||||
 | 
							for i, c := range q.where {
 | 
				
			||||||
 | 
								argIdx = len(q.args)
 | 
				
			||||||
 | 
								if i > 0 {
 | 
				
			||||||
 | 
									sb.WriteString(" AND ")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								sb.WriteString(c.Condition(&q.args, argIdx))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// GROUP BY
 | 
				
			||||||
 | 
						if len(q.groupBy) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" GROUP BY ")
 | 
				
			||||||
 | 
							sb.WriteString(joinFileds(q.groupBy))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HAVING
 | 
				
			||||||
 | 
						if len(q.having) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" HAVING ")
 | 
				
			||||||
 | 
							var argIdx int
 | 
				
			||||||
 | 
							for i, c := range q.having {
 | 
				
			||||||
 | 
								argIdx = len(q.args)
 | 
				
			||||||
 | 
								if i > 0 {
 | 
				
			||||||
 | 
									sb.WriteString(" AND ")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								sb.WriteString(c.Condition(&q.args, argIdx))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ORDER BY
 | 
				
			||||||
 | 
						if len(q.orderBy) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" ORDER BY ")
 | 
				
			||||||
 | 
							sb.WriteString(joinFileds(q.orderBy))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// LIMIT
 | 
				
			||||||
 | 
						if q.limit > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" LIMIT ")
 | 
				
			||||||
 | 
							sb.WriteString(strconv.Itoa(q.limit))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OFFSET
 | 
				
			||||||
 | 
						if q.offset > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" OFFSET ")
 | 
				
			||||||
 | 
							sb.WriteString(strconv.Itoa(q.offset))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qry := sb.String()
 | 
				
			||||||
 | 
						if q.debug {
 | 
				
			||||||
 | 
							fmt.Println("***")
 | 
				
			||||||
 | 
							fmt.Println(qry)
 | 
				
			||||||
 | 
							fmt.Printf("%+v\n", q.args)
 | 
				
			||||||
 | 
							fmt.Println("***")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return qry
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *selectQry) averageLen() int {
 | 
				
			||||||
 | 
						n := 12 + len(q.table) // SELECT FROM <tbl_name>
 | 
				
			||||||
 | 
						for _, c := range q.fields {
 | 
				
			||||||
 | 
							n += (len(c) + 2) * len(q.table) // columns with tablename.columnname
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// JOIN
 | 
				
			||||||
 | 
						if len(q.join) > 0 {
 | 
				
			||||||
 | 
							for _, c := range q.join {
 | 
				
			||||||
 | 
								n += len(c) + 2 // with whitespace
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// WHERE
 | 
				
			||||||
 | 
						if len(q.where) > 0 {
 | 
				
			||||||
 | 
							n += 7 + len(q.args)*5 // WHERE with 2 sapces and each args roughly with min of 5 char
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// GROUP BY
 | 
				
			||||||
 | 
						if len(q.groupBy) > 0 {
 | 
				
			||||||
 | 
							n += 10 // GROUP BY
 | 
				
			||||||
 | 
							for _, c := range q.groupBy {
 | 
				
			||||||
 | 
								n += len(c) + 2 // one command and a whitespace
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ORDER BY
 | 
				
			||||||
 | 
						if len(q.orderBy) > 0 {
 | 
				
			||||||
 | 
							n += 10 // ORDER BY
 | 
				
			||||||
 | 
							for _, c := range q.orderBy {
 | 
				
			||||||
 | 
								n += len(c) + 2 // one command and a whitespace
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// LIMIT
 | 
				
			||||||
 | 
						if q.limit > 0 {
 | 
				
			||||||
 | 
							n += 10 // LIMIT
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// OFFSET
 | 
				
			||||||
 | 
						if q.offset > 0 {
 | 
				
			||||||
 | 
							n += 10 // OFFSET
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return n
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										100
									
								
								qry_update.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								qry_update.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
				
			|||||||
 | 
					// Patial Tech.
 | 
				
			||||||
 | 
					// Author, Ankit Patial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package pgm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"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),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return qb
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *updateQry) Set(field Field, val any) UpdateClause {
 | 
				
			||||||
 | 
						col := field.Name()
 | 
				
			||||||
 | 
						q.cols = append(q.cols, col+"=$"+strconv.Itoa(len(q.args)+1))
 | 
				
			||||||
 | 
						q.args = append(q.args, val)
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *updateQry) SetMap(cols map[Field]any) UpdateClause {
 | 
				
			||||||
 | 
						for k, v := range cols {
 | 
				
			||||||
 | 
							q.Set(k, v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *updateQry) Where(cond ...Conditioner) WhereOrExec {
 | 
				
			||||||
 | 
						q.condition = append(q.condition, cond...)
 | 
				
			||||||
 | 
						return q
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *updateQry) Exec(ctx context.Context) error {
 | 
				
			||||||
 | 
						_, err := poolPGX.Load().Exec(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *updateQry) ExecTx(ctx context.Context, tx pgx.Tx) error {
 | 
				
			||||||
 | 
						_, err := tx.Exec(ctx, q.String(), q.args...)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (q *updateQry) String() string {
 | 
				
			||||||
 | 
						sb := getSB()
 | 
				
			||||||
 | 
						defer putSB(sb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						n := 7 + len(q.table) + 5 // "UPDATE q.table SET
 | 
				
			||||||
 | 
						for _, col := range q.cols {
 | 
				
			||||||
 | 
							n += len(col) + 5
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(q.condition) > 0 {
 | 
				
			||||||
 | 
							n += 7 + len(q.condition)*5 // WHERE with condition, 5 is just avg small min val
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sb.Grow(n)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// UPDATE
 | 
				
			||||||
 | 
						sb.WriteString("UPDATE " + q.table + " SET ")
 | 
				
			||||||
 | 
						sb.WriteString(strings.Join(q.cols, ", "))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// WHERE
 | 
				
			||||||
 | 
						if len(q.condition) > 0 {
 | 
				
			||||||
 | 
							sb.WriteString(" WHERE ")
 | 
				
			||||||
 | 
							var argIdx int
 | 
				
			||||||
 | 
							for i, c := range q.condition {
 | 
				
			||||||
 | 
								argIdx = len(q.args)
 | 
				
			||||||
 | 
								if i > 0 {
 | 
				
			||||||
 | 
									sb.WriteString(" AND ")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								sb.WriteString(c.Condition(&q.args, argIdx))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sb.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user