{"id":28441191,"url":"https://github.com/allisson/sqlutil","last_synced_at":"2026-02-15T15:42:02.613Z","repository":{"id":46011255,"uuid":"388422101","full_name":"allisson/sqlutil","owner":"allisson","description":"A collection of helpers to deal with database.","archived":false,"fork":false,"pushed_at":"2024-02-15T12:48:16.000Z","size":50,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-06T04:09:06.443Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/allisson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-22T10:28:33.000Z","updated_at":"2023-01-18T15:42:29.000Z","dependencies_parsed_at":"2024-02-15T13:48:05.558Z","dependency_job_id":"ca400e33-f9c0-4171-864b-4f26d3d0c5cd","html_url":"https://github.com/allisson/sqlutil","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/allisson/sqlutil","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fsqlutil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fsqlutil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fsqlutil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fsqlutil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/allisson","download_url":"https://codeload.github.com/allisson/sqlutil/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/allisson%2Fsqlutil/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265228985,"owners_count":23731091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-06T04:09:06.253Z","updated_at":"2026-02-15T15:42:02.588Z","avatar_url":"https://github.com/allisson.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sqlutil\n\n[![Build Status](https://github.com/allisson/sqlutil/workflows/Release/badge.svg)](https://github.com/allisson/sqlutil/actions)\n[![Go Report Card](https://goreportcard.com/badge/github.com/allisson/sqlutil)](https://goreportcard.com/report/github.com/allisson/sqlutil)\n[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white\u0026style=flat-square)](https://pkg.go.dev/github.com/allisson/sqlutil)\n\nA powerful and flexible collection of Go helpers for database operations. sqlutil simplifies common database tasks with a clean, chainable API that works with PostgreSQL, MySQL, and SQLite.\n\n## Features\n\n- Clean, intuitive API for CRUD operations\n- Support for multiple SQL flavors (PostgreSQL, MySQL, SQLite)\n- Flexible filtering with comparison operators (=, \u003c\u003e, \u003e, \u003c, \u003e=, \u003c=, LIKE, IN, NOT IN)\n- Pagination and ordering support\n- Field selection to retrieve only needed columns\n- Row locking support (FOR UPDATE)\n- Works with `*sql.DB`, `*sql.Conn`, and `*sql.Tx`\n- Built on top of [sqlquery](https://github.com/allisson/sqlquery) and [scany](https://github.com/georgysavva/scany)\n\n## Installation\n\n```bash\ngo get github.com/allisson/sqlutil\n```\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Database Setup](#database-setup)\n- [Basic CRUD Operations](#basic-crud-operations)\n  - [Insert](#insert)\n  - [Get (Single Record)](#get-single-record)\n  - [Select (Multiple Records)](#select-multiple-records)\n  - [Update](#update)\n  - [Delete](#delete)\n- [Advanced Queries](#advanced-queries)\n  - [Filtering](#filtering)\n  - [Field Selection](#field-selection)\n  - [Pagination](#pagination)\n  - [Ordering](#ordering)\n  - [Row Locking](#row-locking)\n- [Bulk Operations](#bulk-operations)\n- [Using with Transactions](#using-with-transactions)\n- [Multiple SQL Flavors](#multiple-sql-flavors)\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/allisson/sqlutil\"\n\t_ \"github.com/lib/pq\"\n)\n\ntype User struct {\n\tID    int    `db:\"id\"`\n\tName  string `db:\"name\" fieldtag:\"insert,update\"`\n\tEmail string `db:\"email\" fieldtag:\"insert,update\"`\n\tAge   int    `db:\"age\" fieldtag:\"insert,update\"`\n}\n\nfunc main() {\n\t// Connect to database\n\tdb, err := sql.Open(\"postgres\", \"postgres://user:password@localhost/mydb?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer db.Close()\n\n\tctx := context.Background()\n\tflavor := sqlutil.PostgreSQLFlavor\n\n\t// Insert a user\n\tuser := User{Name: \"Alice\", Email: \"alice@example.com\", Age: 30}\n\tif err := sqlutil.Insert(ctx, db, flavor, \"insert\", \"users\", \u0026user); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Get a user\n\tvar alice User\n\topts := sqlutil.NewFindOptions(flavor).WithFilter(\"email\", \"alice@example.com\")\n\tif err := sqlutil.Get(ctx, db, \"users\", opts, \u0026alice); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"Found user: %+v\\n\", alice)\n\n\t// Update the user\n\talice.Age = 31\n\tif err := sqlutil.Update(ctx, db, flavor, \"update\", \"users\", alice.ID, \u0026alice); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Delete the user\n\tif err := sqlutil.Delete(ctx, db, flavor, \"users\", alice.ID); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\n\n## Database Setup\n\nFor the examples below, we'll use this table structure:\n\n```sql\nCREATE TABLE users (\n\tid SERIAL PRIMARY KEY,\n\tname VARCHAR(255) NOT NULL,\n\temail VARCHAR(255) UNIQUE NOT NULL,\n\tage INTEGER NOT NULL,\n\tcountry VARCHAR(100),\n\tactive BOOLEAN DEFAULT true,\n\tcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n```\n\nRun a PostgreSQL database with Docker:\n\n```bash\ndocker run --name sqlutil-postgres \\\n\t-e POSTGRES_USER=user \\\n\t-e POSTGRES_PASSWORD=password \\\n\t-e POSTGRES_DB=sqlutil \\\n\t-p 5432:5432 \\\n\t-d postgres:14-alpine\n```\n\n## Basic CRUD Operations\n\n### Insert\n\nInsert a single record into the database:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"log\"\n\n\t\"github.com/allisson/sqlutil\"\n\t_ \"github.com/lib/pq\"\n)\n\ntype User struct {\n\tID        int    `db:\"id\"`\n\tName      string `db:\"name\" fieldtag:\"insert,update\"`\n\tEmail     string `db:\"email\" fieldtag:\"insert,update\"`\n\tAge       int    `db:\"age\" fieldtag:\"insert,update\"`\n\tCountry   string `db:\"country\" fieldtag:\"insert,update\"`\n\tActive    bool   `db:\"active\" fieldtag:\"insert,update\"`\n}\n\nfunc main() {\n\tdb, _ := sql.Open(\"postgres\", \"postgres://user:password@localhost/sqlutil?sslmode=disable\")\n\tdefer db.Close()\n\n\tctx := context.Background()\n\tflavor := sqlutil.PostgreSQLFlavor\n\n\t// Insert a new user\n\tnewUser := User{\n\t\tName:    \"Bob Smith\",\n\t\tEmail:   \"bob@example.com\",\n\t\tAge:     28,\n\t\tCountry: \"USA\",\n\t\tActive:  true,\n\t}\n\n\t// The \"insert\" tag tells sqlutil to use fields marked with fieldtag:\"insert\"\n\tif err := sqlutil.Insert(ctx, db, flavor, \"insert\", \"users\", \u0026newUser); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlog.Println(\"User inserted successfully\")\n}\n```\n\n### Get (Single Record)\n\nRetrieve a single record from the database:\n\n```go\n// Get user by ID\nvar user User\nopts := sqlutil.NewFindOptions(flavor).WithFilter(\"id\", 1)\nif err := sqlutil.Get(ctx, db, \"users\", opts, \u0026user); err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"User: %+v\\n\", user)\n\n// Get user by email\nvar userByEmail User\nopts = sqlutil.NewFindOptions(flavor).WithFilter(\"email\", \"bob@example.com\")\nif err := sqlutil.Get(ctx, db, \"users\", opts, \u0026userByEmail); err != nil {\n\tlog.Fatal(err)\n}\n\n// Get user with specific fields only\nvar partialUser struct {\n\tID    int    `db:\"id\"`\n\tName  string `db:\"name\"`\n\tEmail string `db:\"email\"`\n}\nopts = sqlutil.NewFindOptions(flavor).\n\tWithFields([]string{\"id\", \"name\", \"email\"}).\n\tWithFilter(\"id\", 1)\nif err := sqlutil.Get(ctx, db, \"users\", opts, \u0026partialUser); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Select (Multiple Records)\n\nRetrieve multiple records from the database:\n\n```go\n// Get all users\nvar users []User\nopts := sqlutil.NewFindAllOptions(flavor)\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026users); err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"Found %d users\\n\", len(users))\n\n// Get active users only\nvar activeUsers []User\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"active\", true)\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026activeUsers); err != nil {\n\tlog.Fatal(err)\n}\n\n// Get users with pagination\nvar pagedUsers []User\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithLimit(10).\n\tWithOffset(0).\n\tWithOrderBy(\"created_at DESC\")\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026pagedUsers); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Update\n\nUpdate existing records:\n\n```go\n// Update user by ID\nuser := User{\n\tID:      1,\n\tName:    \"Bob Smith Jr.\",\n\tEmail:   \"bob.jr@example.com\",\n\tAge:     29,\n\tCountry: \"USA\",\n\tActive:  true,\n}\n\n// The \"update\" tag tells sqlutil to use fields marked with fieldtag:\"update\"\nif err := sqlutil.Update(ctx, db, flavor, \"update\", \"users\", user.ID, \u0026user); err != nil {\n\tlog.Fatal(err)\n}\n\n// Update with custom options (more flexible)\nupdateOpts := sqlutil.NewUpdateOptions(flavor).\n\tWithSet(\"age\", 30).\n\tWithSet(\"country\", \"Canada\").\n\tWithFilter(\"id\", 1)\n\nif err := sqlutil.UpdateWithOptions(ctx, db, flavor, \"users\", updateOpts); err != nil {\n\tlog.Fatal(err)\n}\n\n// Bulk update - update all inactive users\nupdateOpts = sqlutil.NewUpdateOptions(flavor).\n\tWithSet(\"active\", false).\n\tWithFilter(\"age.lt\", 18)\n\nif err := sqlutil.UpdateWithOptions(ctx, db, flavor, \"users\", updateOpts); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Delete\n\nDelete records from the database:\n\n```go\n// Delete user by ID\nif err := sqlutil.Delete(ctx, db, flavor, \"users\", 1); err != nil {\n\tlog.Fatal(err)\n}\n\n// Delete with custom options (more flexible)\ndeleteOpts := sqlutil.NewDeleteOptions(flavor).WithFilter(\"active\", false)\nif err := sqlutil.DeleteWithOptions(ctx, db, flavor, \"users\", deleteOpts); err != nil {\n\tlog.Fatal(err)\n}\n\n// Delete users older than 65\ndeleteOpts = sqlutil.NewDeleteOptions(flavor).WithFilter(\"age.gt\", 65)\nif err := sqlutil.DeleteWithOptions(ctx, db, flavor, \"users\", deleteOpts); err != nil {\n\tlog.Fatal(err)\n}\n\n// Delete users from specific country\ndeleteOpts = sqlutil.NewDeleteOptions(flavor).WithFilter(\"country\", \"USA\")\nif err := sqlutil.DeleteWithOptions(ctx, db, flavor, \"users\", deleteOpts); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n## Advanced Queries\n\n### Filtering\n\nsqlutil supports a wide range of filter operators:\n\n```go\nctx := context.Background()\nflavor := sqlutil.PostgreSQLFlavor\n\n// Equality\nopts := sqlutil.NewFindAllOptions(flavor).WithFilter(\"age\", 30)\n// WHERE age = 30\n\n// Not equal\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"age.not\", 30)\n// WHERE age \u003c\u003e 30\n\n// Greater than\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"age.gt\", 18)\n// WHERE age \u003e 18\n\n// Greater than or equal\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"age.gte\", 18)\n// WHERE age \u003e= 18\n\n// Less than\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"age.lt\", 65)\n// WHERE age \u003c 65\n\n// Less than or equal\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"age.lte\", 65)\n// WHERE age \u003c= 65\n\n// LIKE operator\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"name.like\", \"%Smith%\")\n// WHERE name LIKE '%Smith%'\n\n// IN operator\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"country.in\", \"USA,Canada,Mexico\")\n// WHERE country IN ('USA', 'Canada', 'Mexico')\n\n// NOT IN operator\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"country.notin\", \"USA,Canada\")\n// WHERE country NOT IN ('USA', 'Canada')\n\n// IS NULL\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"country.null\", true)\n// WHERE country IS NULL\n\n// IS NOT NULL\nopts = sqlutil.NewFindAllOptions(flavor).WithFilter(\"country.null\", false)\n// WHERE country IS NOT NULL\n\n// Multiple filters (AND condition)\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"age.gte\", 18).\n\tWithFilter(\"age.lte\", 65).\n\tWithFilter(\"active\", true).\n\tWithFilter(\"country\", \"USA\")\n// WHERE age \u003e= 18 AND age \u003c= 65 AND active = true AND country = 'USA'\n\n// Complex example: Find active users aged 25-40 from specific countries\nvar users []User\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"active\", true).\n\tWithFilter(\"age.gte\", 25).\n\tWithFilter(\"age.lte\", 40).\n\tWithFilter(\"country.in\", \"USA,Canada,UK\")\n\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026users); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Field Selection\n\nRetrieve only the fields you need:\n\n```go\n// Define a struct with only the fields you need\ntype UserBasic struct {\n\tID    int    `db:\"id\"`\n\tName  string `db:\"name\"`\n\tEmail string `db:\"email\"`\n}\n\n// Select only specific fields\nvar users []UserBasic\nopts := sqlutil.NewFindAllOptions(flavor).\n\tWithFields([]string{\"id\", \"name\", \"email\"}).\n\tWithFilter(\"active\", true)\n\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026users); err != nil {\n\tlog.Fatal(err)\n}\n// SELECT id, name, email FROM users WHERE active = true\n\n// Get single user with limited fields\nvar userBasic UserBasic\nopts := sqlutil.NewFindOptions(flavor).\n\tWithFields([]string{\"id\", \"name\", \"email\"}).\n\tWithFilter(\"id\", 1)\n\nif err := sqlutil.Get(ctx, db, \"users\", opts, \u0026userBasic); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Pagination\n\nImplement pagination for large result sets:\n\n```go\n// Page 1: First 10 users\nvar page1 []User\nopts := sqlutil.NewFindAllOptions(flavor).\n\tWithLimit(10).\n\tWithOffset(0).\n\tWithOrderBy(\"id ASC\")\n\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026page1); err != nil {\n\tlog.Fatal(err)\n}\n\n// Page 2: Next 10 users\nvar page2 []User\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithLimit(10).\n\tWithOffset(10).\n\tWithOrderBy(\"id ASC\")\n\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026page2); err != nil {\n\tlog.Fatal(err)\n}\n\n// Pagination helper function\nfunc GetUserPage(ctx context.Context, db *sql.DB, page, pageSize int) ([]User, error) {\n\tvar users []User\n\toffset := (page - 1) * pageSize\n\t\n\topts := sqlutil.NewFindAllOptions(flavor).\n\t\tWithLimit(pageSize).\n\t\tWithOffset(offset).\n\t\tWithOrderBy(\"id ASC\")\n\t\n\terr := sqlutil.Select(ctx, db, \"users\", opts, \u0026users)\n\treturn users, err\n}\n\n// Usage\npage1Users, err := GetUserPage(ctx, db, 1, 20) // First page, 20 items\npage2Users, err := GetUserPage(ctx, db, 2, 20) // Second page, 20 items\n```\n\n### Ordering\n\nSort results by one or multiple columns:\n\n```go\n// Order by single column ascending\nopts := sqlutil.NewFindAllOptions(flavor).WithOrderBy(\"name ASC\")\n\n// Order by single column descending\nopts = sqlutil.NewFindAllOptions(flavor).WithOrderBy(\"created_at DESC\")\n\n// Order by multiple columns\nopts = sqlutil.NewFindAllOptions(flavor).WithOrderBy(\"country ASC, age DESC\")\n\n// Complex example: Get top 10 youngest active users from USA\nvar youngUsers []User\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"active\", true).\n\tWithFilter(\"country\", \"USA\").\n\tWithOrderBy(\"age ASC\").\n\tWithLimit(10)\n\nif err := sqlutil.Select(ctx, db, \"users\", opts, \u0026youngUsers); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n### Row Locking\n\nUse row locking for concurrent operations:\n\n```go\n// FOR UPDATE - locks rows for update\nopts := sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"active\", true).\n\tWithForUpdate(\"\")\n\n// FOR UPDATE SKIP LOCKED - skip rows that are already locked\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"active\", true).\n\tWithForUpdate(\"SKIP LOCKED\")\n\n// FOR UPDATE NOWAIT - return immediately if rows are locked\nopts = sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"active\", true).\n\tWithForUpdate(\"NOWAIT\")\n\n// Example: Process queue items with row locking\ntx, err := db.BeginTx(ctx, nil)\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer tx.Rollback()\n\nvar queueItems []QueueItem\nopts := sqlutil.NewFindAllOptions(flavor).\n\tWithFilter(\"status\", \"pending\").\n\tWithOrderBy(\"created_at ASC\").\n\tWithLimit(10).\n\tWithForUpdate(\"SKIP LOCKED\")\n\nif err := sqlutil.Select(ctx, tx, \"queue\", opts, \u0026queueItems); err != nil {\n\tlog.Fatal(err)\n}\n\n// Process items...\nfor _, item := range queueItems {\n\t// Process item\n\tupdateOpts := sqlutil.NewUpdateOptions(flavor).\n\t\tWithSet(\"status\", \"processed\").\n\t\tWithFilter(\"id\", item.ID)\n\t\n\tif err := sqlutil.UpdateWithOptions(ctx, tx, flavor, \"queue\", updateOpts); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\ntx.Commit()\n```\n\n## Bulk Operations\n\nEfficiently handle multiple operations:\n\n```go\n// Insert multiple users\nusers := []User{\n\t{Name: \"Alice\", Email: \"alice@example.com\", Age: 30, Country: \"USA\"},\n\t{Name: \"Bob\", Email: \"bob@example.com\", Age: 25, Country: \"Canada\"},\n\t{Name: \"Charlie\", Email: \"charlie@example.com\", Age: 35, Country: \"UK\"},\n}\n\nfor _, user := range users {\n\tif err := sqlutil.Insert(ctx, db, flavor, \"insert\", \"users\", \u0026user); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\n// Bulk update with filter\nupdateOpts := sqlutil.NewUpdateOptions(flavor).\n\tWithSet(\"active\", false).\n\tWithFilter(\"country.in\", \"USA,Canada,Mexico\")\n\nif err := sqlutil.UpdateWithOptions(ctx, db, flavor, \"users\", updateOpts); err != nil {\n\tlog.Fatal(err)\n}\n\n// Bulk delete with filter\ndeleteOpts := sqlutil.NewDeleteOptions(flavor).\n\tWithFilter(\"active\", false).\n\tWithFilter(\"age.lt\", 18)\n\nif err := sqlutil.DeleteWithOptions(ctx, db, flavor, \"users\", deleteOpts); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n## Using with Transactions\n\nsqlutil works seamlessly with database transactions:\n\n```go\n// Start a transaction\ntx, err := db.BeginTx(ctx, nil)\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer tx.Rollback() // Rollback if not committed\n\n// Insert user\nuser := User{Name: \"David\", Email: \"david@example.com\", Age: 40, Country: \"France\"}\nif err := sqlutil.Insert(ctx, tx, flavor, \"insert\", \"users\", \u0026user); err != nil {\n\tlog.Fatal(err)\n}\n\n// Get the inserted user\nvar insertedUser User\nopts := sqlutil.NewFindOptions(flavor).WithFilter(\"email\", \"david@example.com\")\nif err := sqlutil.Get(ctx, tx, \"users\", opts, \u0026insertedUser); err != nil {\n\tlog.Fatal(err)\n}\n\n// Update the user\ninsertedUser.Age = 41\nif err := sqlutil.Update(ctx, tx, flavor, \"update\", \"users\", insertedUser.ID, \u0026insertedUser); err != nil {\n\tlog.Fatal(err)\n}\n\n// Commit the transaction\nif err := tx.Commit(); err != nil {\n\tlog.Fatal(err)\n}\n\n// Example: Transfer operation with rollback\nfunc TransferUser(ctx context.Context, db *sql.DB, userID int, newCountry string) error {\n\ttx, err := db.BeginTx(ctx, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer tx.Rollback()\n\n\t// Get user\n\tvar user User\n\topts := sqlutil.NewFindOptions(flavor).WithFilter(\"id\", userID)\n\tif err := sqlutil.Get(ctx, tx, \"users\", opts, \u0026user); err != nil {\n\t\treturn err\n\t}\n\n\t// Update country\n\tupdateOpts := sqlutil.NewUpdateOptions(flavor).\n\t\tWithSet(\"country\", newCountry).\n\t\tWithFilter(\"id\", userID)\n\t\n\tif err := sqlutil.UpdateWithOptions(ctx, tx, flavor, \"users\", updateOpts); err != nil {\n\t\treturn err\n\t}\n\n\t// Commit transaction\n\treturn tx.Commit()\n}\n```\n\n## Multiple SQL Flavors\n\nsqlutil supports PostgreSQL, MySQL, and SQLite:\n\n### PostgreSQL\n\n```go\nimport (\n\t\"github.com/allisson/sqlutil\"\n\t_ \"github.com/lib/pq\"\n)\n\ndb, _ := sql.Open(\"postgres\", \"postgres://user:password@localhost/mydb?sslmode=disable\")\nflavor := sqlutil.PostgreSQLFlavor\n\nopts := sqlutil.NewFindOptions(flavor).WithFilter(\"id\", 1)\n```\n\n### MySQL\n\n```go\nimport (\n\t\"github.com/allisson/sqlutil\"\n\t_ \"github.com/go-sql-driver/mysql\"\n)\n\ndb, _ := sql.Open(\"mysql\", \"user:password@tcp(localhost:3306)/mydb\")\nflavor := sqlutil.MySQLFlavor\n\nopts := sqlutil.NewFindOptions(flavor).WithFilter(\"id\", 1)\n```\n\n### SQLite\n\n```go\nimport (\n\t\"github.com/allisson/sqlutil\"\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\ndb, _ := sql.Open(\"sqlite3\", \"./mydb.db\")\nflavor := sqlutil.SQLiteFlavor\n\nopts := sqlutil.NewFindOptions(flavor).WithFilter(\"id\", 1)\n```\n\n### Cross-Database Compatibility\n\nWrite database-agnostic code by passing the flavor as a parameter:\n\n```go\ntype UserRepository struct {\n\tdb     *sql.DB\n\tflavor sqlutil.Flavor\n}\n\nfunc NewUserRepository(db *sql.DB, flavor sqlutil.Flavor) *UserRepository {\n\treturn \u0026UserRepository{db: db, flavor: flavor}\n}\n\nfunc (r *UserRepository) GetByID(ctx context.Context, id int) (*User, error) {\n\tvar user User\n\topts := sqlutil.NewFindOptions(r.flavor).WithFilter(\"id\", id)\n\terr := sqlutil.Get(ctx, r.db, \"users\", opts, \u0026user)\n\treturn \u0026user, err\n}\n\nfunc (r *UserRepository) GetActiveUsers(ctx context.Context) ([]User, error) {\n\tvar users []User\n\topts := sqlutil.NewFindAllOptions(r.flavor).\n\t\tWithFilter(\"active\", true).\n\t\tWithOrderBy(\"name ASC\")\n\terr := sqlutil.Select(ctx, r.db, \"users\", opts, \u0026users)\n\treturn users, err\n}\n\n// Works with any database flavor\npgRepo := NewUserRepository(pgDB, sqlutil.PostgreSQLFlavor)\nmysqlRepo := NewUserRepository(mysqlDB, sqlutil.MySQLFlavor)\nsqliteRepo := NewUserRepository(sqliteDB, sqlutil.SQLiteFlavor)\n```\n\n## Complete Example\n\nHere's a complete example demonstrating various features:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/allisson/sqlutil\"\n\t_ \"github.com/lib/pq\"\n)\n\ntype User struct {\n\tID        int    `db:\"id\"`\n\tName      string `db:\"name\" fieldtag:\"insert,update\"`\n\tEmail     string `db:\"email\" fieldtag:\"insert,update\"`\n\tAge       int    `db:\"age\" fieldtag:\"insert,update\"`\n\tCountry   string `db:\"country\" fieldtag:\"insert,update\"`\n\tActive    bool   `db:\"active\" fieldtag:\"insert,update\"`\n}\n\nfunc main() {\n\t// Connect to database\n\tdb, err := sql.Open(\"postgres\", \"postgres://user:password@localhost/sqlutil?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer db.Close()\n\n\tctx := context.Background()\n\tflavor := sqlutil.PostgreSQLFlavor\n\n\t// Create table\n\t_, err = db.ExecContext(ctx, `\n\t\tCREATE TABLE IF NOT EXISTS users (\n\t\t\tid SERIAL PRIMARY KEY,\n\t\t\tname VARCHAR(255) NOT NULL,\n\t\t\temail VARCHAR(255) UNIQUE NOT NULL,\n\t\t\tage INTEGER NOT NULL,\n\t\t\tcountry VARCHAR(100),\n\t\t\tactive BOOLEAN DEFAULT true\n\t\t)\n\t`)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Insert users\n\tusers := []User{\n\t\t{Name: \"Alice Johnson\", Email: \"alice@example.com\", Age: 30, Country: \"USA\", Active: true},\n\t\t{Name: \"Bob Smith\", Email: \"bob@example.com\", Age: 25, Country: \"Canada\", Active: true},\n\t\t{Name: \"Charlie Brown\", Email: \"charlie@example.com\", Age: 35, Country: \"UK\", Active: true},\n\t\t{Name: \"David Lee\", Email: \"david@example.com\", Age: 28, Country: \"USA\", Active: false},\n\t}\n\n\tfor _, user := range users {\n\t\tif err := sqlutil.Insert(ctx, db, flavor, \"insert\", \"users\", \u0026user); err != nil {\n\t\t\tlog.Printf(\"Error inserting user: %v\", err)\n\t\t}\n\t}\n\n\t// Get single user by email\n\tvar alice User\n\topts := sqlutil.NewFindOptions(flavor).WithFilter(\"email\", \"alice@example.com\")\n\tif err := sqlutil.Get(ctx, db, \"users\", opts, \u0026alice); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"Found user: %+v\\n\", alice)\n\n\t// Get all active users from USA\n\tvar activeUSAUsers []User\n\topts2 := sqlutil.NewFindAllOptions(flavor).\n\t\tWithFilter(\"active\", true).\n\t\tWithFilter(\"country\", \"USA\").\n\t\tWithOrderBy(\"name ASC\")\n\t\n\tif err := sqlutil.Select(ctx, db, \"users\", opts2, \u0026activeUSAUsers); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"Active USA users: %d\\n\", len(activeUSAUsers))\n\n\t// Get users with age between 25 and 30\n\tvar youngUsers []User\n\topts3 := sqlutil.NewFindAllOptions(flavor).\n\t\tWithFilter(\"age.gte\", 25).\n\t\tWithFilter(\"age.lte\", 30).\n\t\tWithOrderBy(\"age ASC\")\n\t\n\tif err := sqlutil.Select(ctx, db, \"users\", opts3, \u0026youngUsers); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"Users aged 25-30: %d\\n\", len(youngUsers))\n\n\t// Update user\n\talice.Age = 31\n\talice.Country = \"Canada\"\n\tif err := sqlutil.Update(ctx, db, flavor, \"update\", \"users\", alice.ID, \u0026alice); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Bulk update: deactivate all users older than 30\n\tupdateOpts := sqlutil.NewUpdateOptions(flavor).\n\t\tWithSet(\"active\", false).\n\t\tWithFilter(\"age.gt\", 30)\n\t\n\tif err := sqlutil.UpdateWithOptions(ctx, db, flavor, \"users\", updateOpts); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Get users with pagination\n\tpage1 := []User{}\n\tpaginationOpts := sqlutil.NewFindAllOptions(flavor).\n\t\tWithLimit(2).\n\t\tWithOffset(0).\n\t\tWithOrderBy(\"name ASC\")\n\t\n\tif err := sqlutil.Select(ctx, db, \"users\", paginationOpts, \u0026page1); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"Page 1: %d users\\n\", len(page1))\n\n\t// Delete inactive users\n\tdeleteOpts := sqlutil.NewDeleteOptions(flavor).WithFilter(\"active\", false)\n\tif err := sqlutil.DeleteWithOptions(ctx, db, flavor, \"users\", deleteOpts); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Println(\"Inactive users deleted\")\n}\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Related Projects\n\n- [sqlquery](https://github.com/allisson/sqlquery) - SQL query builder for Go\n- [scany](https://github.com/georgysavva/scany) - Library for scanning data from a database into Go structs\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallisson%2Fsqlutil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fallisson%2Fsqlutil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fallisson%2Fsqlutil/lists"}