{"id":30865100,"url":"https://github.com/gandaldf/sqlr","last_synced_at":"2026-04-02T03:02:53.642Z","repository":{"id":312689547,"uuid":"1048094631","full_name":"gandaldf/sqlr","owner":"gandaldf","description":"A minimal SQL-first builder/mapper for Go: parameter binding, bulk expansion, fast struct scans — no DSL, no heavy ORM.","archived":false,"fork":false,"pushed_at":"2025-09-01T10:36:59.000Z","size":53,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-01T12:41:39.029Z","etag":null,"topics":["database","golang","mysql","postgres","query-builder","sql","sql-builder","sqlite","sqlserver"],"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/gandaldf.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-31T22:41:25.000Z","updated_at":"2025-09-01T10:44:49.000Z","dependencies_parsed_at":"2025-09-01T12:51:43.203Z","dependency_job_id":null,"html_url":"https://github.com/gandaldf/sqlr","commit_stats":null,"previous_names":["gandaldf/sqlr"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/gandaldf/sqlr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gandaldf%2Fsqlr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gandaldf%2Fsqlr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gandaldf%2Fsqlr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gandaldf%2Fsqlr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gandaldf","download_url":"https://codeload.github.com/gandaldf/sqlr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gandaldf%2Fsqlr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31294826,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T01:43:37.129Z","status":"online","status_checked_at":"2026-04-02T02:00:08.535Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["database","golang","mysql","postgres","query-builder","sql","sql-builder","sqlite","sqlserver"],"created_at":"2025-09-07T20:50:51.037Z","updated_at":"2026-04-02T03:02:53.603Z","avatar_url":"https://github.com/gandaldf.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sqlr — a tiny, SQL-first builder \u0026 mapper for Go\n[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/gandaldf/sqlr/blob/master/LICENSE)\n[![Go Report Card](https://goreportcard.com/badge/github.com/gandaldf/sqlr)](https://goreportcard.com/report/github.com/gandaldf/sqlr)\n[![Go Reference](https://pkg.go.dev/badge/github.com/gandaldf/sqlr.svg)](https://pkg.go.dev/github.com/gandaldf/sqlr)\n[![Version](https://img.shields.io/github/tag/gandaldf/sqlr.svg?color=blue\u0026label=version)](https://github.com/gandaldf/sqlr/releases)\n\nsqlr is a minimal SQL builder and result mapper designed to stay very close to the SQL you already write.\nIt focuses on keeping things simple: turn :named placeholders into driver args, expand IN (...) automatically, support bulk VALUES, and scan rows into your structs efficiently — all without a heavy ORM or a fluent DSL.\n\n## Features:\n\n- SQL-first, no DSL: you write the SQL, sqlr doesn’t invent a DSL; it just binds and scans.\n- Multiple dialects: Postgres, MySQL, SQLite, SQL Server.\n- Placeholder rendering per dialect: Postgres → $1,$2…; MySQL/SQLite → ?; SQL Server → @p1,@p2….\n- Minimal API surface: New, Write/Writef, Bind, Preview/Build, Exec, ScanOne, ScanAll.\n- Typed scans, fast: struct mapping via db tags or field names, nested struct flattening, pointer/null handling.\n- Bulk insert made simple: :name{a,b,c} emits VALUES (...),(...),... with bound args.\n- Plays well with handcrafted SQL (CTEs, JSON ops, window functions…).\n- No external dependencies: only the standard library.\n- Performance-minded: single-pass parser, sync.Pool builders, cached struct plans, careful allocation.\n- Safe by design: values are never interpolated into SQL strings; everything is parameterized.\n- Concurrency: share one *SQLR across goroutines; each *Builder is single-use.\n\n## Installation:\n```\ngo get github.com/gandaldf/sqlr@latest\n```\n\n## Examples:\n\n### Quick start\n```golang\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"log\"\n\n\t_ \"github.com/lib/pq\"\n\t\"github.com/gandaldf/sqlr\"\n)\n\ntype User struct {\n\tID   int    `db:\"id\"`\n\tName string `db:\"name\"`\n}\n\nfunc main() {\n\tdb, _ := sql.Open(\"postgres\", \"\u003cdsn\u003e\")\n\n\tvar users []User\n\terr := sqlr.New(sqlr.Postgres).\n\t\tWrite(\"SELECT id, name FROM users WHERE id IN (:ids) AND active=:active\").\n\t\tBind(\"ids\", []int{1,2,3}).\n\t\tBind(\"active\", true). // later binds can add/override keys\n\t\tScanAll(db, \u0026users)\n\tif err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\n### Execute a statement\n```golang\nres, err := sqlr.New(sqlr.MySQL).\n  Write(\"UPDATE products SET price=:price WHERE id IN (:ids)\").\n  Bind(\"price\", 999, \"ids\", []int{7,8,9}).\n  Exec(db)\nif err != nil { return err }\nrows, _ := res.RowsAffected()\n```\n\n### Read a single scalar\n```golang\nvar count int\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT COUNT(*) FROM orders WHERE customer_id=:c AND status=:s\").\n  Bind(\"c\", 42, \"s\", \"paid\").\n  ScanOne(db, \u0026count)\n```\n\n### One row exactly\n```golang\nvar u User\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT id, name FROM users WHERE email=:e\").\n  Bind(\"e\", email).\n  ScanOne(db, \u0026u)\n// returns sql.ErrNoRows if none; sqlr.ErrMoreThanOneRow if \u003e1\n```\n\n### Struct scans (tags, flattening, NULLs)\n```golang\ntype Audit struct {\n\tCreatedAt time.Time `db:\"created_at\"`\n}\ntype Row struct {\n\tID    int     `db:\"id\"`\n\tName  string  `db:\"name\"`\n\tNote  *string `db:\"note\"` // pointer handles NULL\n\tAudit Audit\n}\n\nvar out []Row\nerr := sqlr.New(sqlr.Postgres).\n  Write(`SELECT id, name, note, created_at FROM users WHERE active=:a`).\n  Bind(\"a\", true).\n  ScanAll(db, \u0026out)\n```\n- created_at maps into Audit.CreatedAt via flattening.\n- Pointers become nil when the DB returns NULL.\n\n### Bulk insert\n```golang\ntype NewUser struct {\n\tID   int    `db:\"id\"`\n\tName string `db:\"name\"`\n}\nrows := []NewUser{{1,\"Anna\"},{2,\"Luca\"},{3,\"Mia\"}}\n\n_, err := sqlr.New(sqlr.SQLite).\n  Write(\"INSERT INTO users (id,name) VALUES :batch{id,name}\").\n  Bind(\"batch\", rows).\n  Exec(db)\n```\nThe placeholder is called ```:batch{...}``` here but it's arbitrary, It's not a keyword, but just a regular named parameter with curly braces.\n\n### Expansion in action\nsqlr expands at build time based on your bound values. You write :named params; sqlr turns them into the right placeholders for the dialect, expands slices/rows, and builds the final args in one pass.\n\n#### IN (...) slice expansion\n```golang\nq, args, _ := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT * FROM t WHERE id IN (:ids) AND active=:a\").\n  Bind(\"ids\", []int{10,11,12}).\n  Bind(\"a\", true).\n  Preview()\n\n// q (pretty-printed):\n// SELECT * FROM t WHERE id IN ($1,$2,$3) AND active=$4\n// args: [10 11 12 true]\n```\n\n#### VALUES :rows{...} bulk expansion\n```golang\ntype NewUser struct{ ID int `db:\"id\"`; Name string `db:\"name\"` }\nrows := []NewUser{{1,\"Anna\"},{2,\"Luca\"},{3,\"Mia\"}}\n\nq, args, _ := sqlr.New(sqlr.Postgres).\n  Write(\"INSERT INTO users (id,name) VALUES :rows{id,name}\").\n  Bind(\"rows\", rows).\n  Preview()\n\n// q:\n// INSERT INTO users (id,name) VALUES ($1,$2),($3,$4),($5,$6)\n// args: [1 \"Anna\" 2 \"Luca\" 3 \"Mia\"]\n```\n\n### Prevent slice expansion (keep one placeholder)\n```golang\nids := []int64{1,2,3}\n\n_, _, _ = sqlr.New(sqlr.Postgres).\n  Write(\"SELECT * FROM t WHERE id = ANY(:ids)\").\n  Bind(\"ids\", sqlr.Scalar(ids)). // keeps a single param\n  Build()\n```\nUsing a driver.Valuer (e.g. pq.Array(ids)) also prevents expansion.\n\n### Scalar binding via struct tag\n```golang\n// Bind a slice as a single scalar param using the \",scalar\" option.\ntype Filter struct {\n\tIDs    []int  `db:\"ids,scalar\"` // \u003c- prevents expansion of :ids\n\tActive bool   `db:\"active\"`\n}\n\nvar out []int\n\nf := Filter{IDs: []int{1, 2, 3}, Active: true}\n\nerr := sqlr.New(sqlr.Postgres).\n  Write(`SELECT id FROM users WHERE id = ANY(:ids) AND active = :active`).\n  Bind(f). // struct tags control binding behavior\n  ScanAll(db, \u0026out)\n```\nThe ```,scalar``` option on the db tag tells sqlr not to expand the slice; it remains one placeholder whose value is the whole slice (or driver.Valuer).\n\n### driver.Valuer (Postgres array)\n```golang\nimport \"github.com/lib/pq\"\n\nids := []int64{1,2,3}\nvar out []int64\n\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT id FROM users WHERE id = ANY(:ids)\").\n  Bind(\"ids\", pq.Array(ids)). // single placeholder; driver handles encoding\n  ScanAll(db, \u0026out)\n```\n\n### Valuer + Scanner (JSONB round-trip)\n```golang\ntype JSONB map[string]any\n\nfunc (j JSONB) Value() (driver.Value, error) { // driver.Valuer\n    b, err := json.Marshal(j)\n    return b, err\n}\nfunc (j *JSONB) Scan(src any) error { // sql.Scanner\n    switch v := src.(type) {\n    case []byte:\n        return json.Unmarshal(v, j)\n    case string:\n        return json.Unmarshal([]byte(v), j)\n    default:\n        return fmt.Errorf(\"unsupported: %T\", src)\n    }\n}\n\ntype Row struct {\n    Meta JSONB `db:\"meta\"`\n}\n\nvar rows []Row\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT meta FROM users WHERE active=:a\").\n  Bind(\"a\", true).\n  ScanAll(db, \u0026rows)\n```\nIn short: Valuer controls how a value is sent to the driver; Scanner controls how a column is read into your type. sqlr lets database/sql do its job here.\n\n### Dynamic composition + Writef()\n```golang\ntable := \"audit_events\" // trusted constant, not user input\n\nb := sqlr.New(sqlr.Postgres).\n  Writef(\"/* tenant=%d */ \", tenantID). // annotate the query\n  Writef(\"SELECT id, ts, kind FROM %s WHERE ts \u003e= :since\", table).\n  Bind(\"since\", time.Now().Add(-6*time.Hour))\n\nsql, args, _ := b.Preview()\n// Use Exec/Scan to run; Preview does not release the builder.\n```\nWritef() is for safe, non-user interpolation (comments, known identifiers). Never put untrusted values in Writef().\n\n### Conditional composition \u0026 many Bind() calls\n```golang\nb := sqlr.New(sqlr.Postgres).\n  Write(`SELECT id, name, created_at FROM users WHERE 1=1`)\n\nif namePrefix != \"\" {\n  b.Write(` AND name ILIKE :name_prefix`).\n    Bind(\"name_prefix\", namePrefix+\"%\")\n}\nif len(ids) \u003e 0 {\n  b.Write(` AND id IN (:ids)`).\n    Bind(\"ids\", ids) // expands only at build time\n}\nif since != nil {\n  b.Write(` AND created_at \u003e= :since`).\n    Bind(\"since\", *since)\n}\n\nvar users []User\nif err := b.ScanAll(db, \u0026users); err != nil { /* ... */ }\n```\nWhy many Bind() calls are cheap\n- Each Bind(...) simply writes keys into an internal bag (map[string]any) owned by the builder. Later binds with the same key overwrite the previous value (last-write-wins).\n- There’s no SQL re-parse and no args slice churn on every Bind. The heavy work happens once at Build/Exec/Scan:\n    - single-pass SQL parse,\n    - placeholder numbering per dialect,\n    - slice/rows expansion,\n    - final []any allocation and fill.\n- Complexity is roughly O(L + H + E) where:\n    - L = SQL length scanned once,\n    - H = number of placeholders resolved via O(1) map lookups,\n    - E = total items produced by expansions (IN (:ids), :rows{...}, etc).\n- Only Bind(struct)/Bind(map) perform reflection or map iteration once per call to materialize/update the bag. Repeated Bind(\"k\", v) pairs are essentially single map writes.\n\nThis design lets you compose queries freely with negligible per-bind overhead, while keeping all value interpolation strictly parameterized.\n\n### JOIN into two structs with overlapping field names\n```golang\ntype User struct {\n\tID   int    `db:\"u_id\"` // note the alias-tag mapping\n\tName string `db:\"u_name\"`\n}\ntype Order struct {\n\tID     int     `db:\"o_id\"` // overlaps on name \"id\", so we alias\n\tTotal  float64 `db:\"total\"`\n}\ntype Row struct {\n\tUser  User\n\tOrder Order\n}\n\nvar rows []Row\nerr := sqlr.New(sqlr.Postgres).\n  Write(`\n    SELECT\n      u.id   AS u_id,\n      u.name AS u_name,\n      o.id   AS o_id,\n      o.total\n    FROM users u\n    JOIN orders o ON o.user_id = u.id\n    WHERE o.status = :st\n  `).\n  Bind(\"st\", \"paid\").\n  ScanAll(db, \u0026rows)\n```\n\n### Alternatives to Bind(\"k\", v)\nWhen you have many parameters—or they already live in a struct/map—it’s often nicer to bind them in one shot instead of writing multiple Bind(\"k\", v) calls. sqlr accepts a literal param map (P{}), any map[string]any, or a struct (using db tags or field names); all end up in the same internal bag, can be mixed freely, and follow last-write-wins when keys overlap.\n\n#### Bind a param map with P{}\n```golang\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT * FROM products WHERE brand=:b AND price\u003c=:p\").\n  Bind(sqlr.P{\"b\": \"Acme\", \"p\": 100}).\n  ScanAll(db, \u0026out)\n```\n\n#### Bind a struct (uses db tags or field names)\n```golang\ntype Filter struct {\n  Brand string `db:\"b\"`\n  MaxP  int    `db:\"p\"`\n}\nf := Filter{\"Acme\", 100}\n\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT * FROM products WHERE brand=:b AND price\u003c=:p\").\n  Bind(f).\n  ScanAll(db, \u0026out)\n```\n\n#### Bind a generic map\n```golang\nm := map[string]any{\"b\": \"Acme\", \"p\": 100}\n\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT * FROM products WHERE brand=:b AND price\u003c=:p\").\n  Bind(m).\n  ScanAll(db, \u0026out)\n```\n\n### ExecContext with timeout\n```golang\nctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\ndefer cancel()\n\nres, err := sqlr.New(sqlr.Postgres).\n  Write(\"UPDATE products SET price=:p WHERE id IN (:ids)\").\n  Bind(\"p\", 999, \"ids\", []int{7,8,9}).\n  ExecContext(ctx, db)\nif err != nil { return err }\n```\n\n### ScanAllContext with cancellation\n```golang\nctx, cancel := context.WithCancel(context.Background())\ndefer cancel()\n\nvar users []User\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT id, name FROM users WHERE active=:a\").\n  Bind(\"a\", true).\n  ScanAllContext(ctx, db, \u0026users)\nif err != nil { return err }\n```\n\n### ScanOneContext with deadline\n```golang\ndeadline := time.Now().Add(500 * time.Millisecond)\nctx, cancel := context.WithDeadline(context.Background(), deadline)\ndefer cancel()\n\nvar count int\nerr := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT COUNT(*) FROM orders WHERE status=:s\").\n  Bind(\"s\", \"paid\").\n  ScanOneContext(ctx, db, \u0026count)\nif err != nil { return err }\n```\n\n### Builder release \u0026 safe reuse\nBuild, Exec and Scan release the builder back to an internal pool. Don’t keep using it after those calls. Use Preview if you need to inspect without releasing.\n\n#### Don’t reuse after Exec/Build\n```golang\nb := sqlr.New(sqlr.Postgres).\n  Write(\"UPDATE t SET a=:a WHERE id=:id\").\n  Bind(\"a\", 1, \"id\", 7)\n\n_, err := b.Exec(db) // releases b\nif err != nil { return err }\n\n// b.Write(\" AND ...\") // DONT'T: b is released\n```\n\n#### Inspect, then execute (Preview doesn’t release)\n```golang\nb := sqlr.New(sqlr.Postgres).\n  Write(\"SELECT * FROM t WHERE id IN (:ids)\").\n  Bind(\"ids\", []int{1,2,3})\n\nq, args, _ := b.Preview() // still usable\n_ = q; _ = args\n\nvar out []int\nif err := b.ScanAll(db, \u0026out); err != nil { /* ... */ } // releases here\n```\n\n#### Start fresh when you need a new query\n```golang\nb := sqlr.New(sqlr.Postgres)\n\n// first query\nif _, err := b.Write(\"DELETE FROM sessions WHERE user_id=:u\").\n  Bind(\"u\", userID).\n  Exec(db); err != nil { return err }\n\n// second query → new builder\nvar user User\nif err := b.Write(\"SELECT id,name FROM users WHERE id=:u\").\n  Bind(\"u\", userID).\n  ScanOne(db, \u0026user); err != nil { return err }\n```\n\n### Transactions\n```golang\nb := sqlr.New(sqlr.Postgres)\n\nctx := context.Background()\ntx, err := db.BeginTx(ctx, nil)\nif err != nil {\n    return err\n}\ndefer tx.Rollback()\n\n// 1) debit\nif _, err := b.Write(\"UPDATE accounts SET balance=balance-:amt WHERE id=:id\").\n  Bind(\"amt\", 50, \"id\", 1001).\n  ExecContext(ctx, tx); err != nil { return err }\n\n// 2) credit\nif _, err := b.Write(\"UPDATE accounts SET balance=balance+:amt WHERE id=:id\").\n  Bind(\"amt\", 50, \"id\", 2002).\n  ExecContext(ctx, tx); err != nil { return err }\n\n// 3) read something within the same tx\nvar total int\nif err := b.Write(\"SELECT COUNT(*) FROM ledger WHERE ok=:ok\").\n  Bind(\"ok\", true).\n  ScanOneContext(ctx, tx, \u0026total); err != nil { return err }\n\nreturn tx.Commit()\n```\n\n## Gotchas \u0026 tips:\n- The *SQLR instance is reusable and thread-safe across the app; each Write() spawns a disposable builder that is released by Build, Exec or Scan.\n- Builder lifecycle: Build, Exec, and Scan release the builder to an internal pool. Don’t reuse it afterward. Use Preview to inspect without releasing.\n- Empty inputs:\n    - IN (:ids) with an empty slice → error (ErrSliceEmpty). Decide your own fallback (WHERE 1=0, omit the clause, etc.).\n    - :name{...} with an empty slice → error (ErrRowsEmpty).\n- Missing binds: referencing :name that isn’t provided yields ErrParamMissing.\n- Ambiguous mapping: two struct fields mapping to the same column name cause ErrFieldAmbiguous. Disambiguate with tags/aliases (as in the JOIN example).\n- NULL into non-pointer: scanning NULL into a non-pointer field triggers a driver scan error. Use *T or sql.Null*.\n- Quotes/comments are respected: :not_a_param inside string literals, comments, or Postgres dollar-quoted blocks is ignored.\n- Writef() safety: only use with trusted literals (comments, known identifiers). Never pass user input to Writef().\n\n## Benchmarks:\n```\nBenchmarkBind_Short_AllDialects/postgres-10              2589922               464.7 ns/op           432 B/op          4 allocs/op\nBenchmarkBind_Short_AllDialects/mysql-10                 2671608               450.6 ns/op           432 B/op          4 allocs/op\nBenchmarkBind_Short_AllDialects/sqlite-10                2670493               448.9 ns/op           432 B/op          4 allocs/op\nBenchmarkBind_Short_AllDialects/sqlserver-10             2579859               467.0 ns/op           432 B/op          4 allocs/op\nBenchmarkBind_Medium_AllDialects/postgres-10              785796              1487 ns/op            1296 B/op         20 allocs/op\nBenchmarkBind_Medium_AllDialects/mysql-10                 793989              1393 ns/op            1280 B/op         20 allocs/op\nBenchmarkBind_Medium_AllDialects/sqlite-10                862000              1371 ns/op            1280 B/op         20 allocs/op\nBenchmarkBind_Medium_AllDialects/sqlserver-10             797533              1501 ns/op            1296 B/op         20 allocs/op\nBenchmarkBind_Long_AllDialects/postgres-10                 17695             67829 ns/op          121531 B/op        534 allocs/op\nBenchmarkBind_Long_AllDialects/mysql-10                    23005             52208 ns/op          104049 B/op        532 allocs/op\nBenchmarkBind_Long_AllDialects/sqlite-10                   22969             52065 ns/op          104048 B/op        532 allocs/op\nBenchmarkBind_Long_AllDialects/sqlserver-10                17173             70306 ns/op          133826 B/op        535 allocs/op\n```\n\n### Performance notes\n- Builders are pooled; scanning uses cached plans and reuses holders to minimize allocations.\n- Field-index lookups are cached in a compact two-tier map.\n- Benchmarks and fuzz tests in the repo guard performance and safety.\n\n## Contributing:\n\nIssues and PRs are welcome — especially additional tests, micro-benchmarks, and dialect edge-cases.\n\n## License:\n\nMIT (see LICENSE).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgandaldf%2Fsqlr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgandaldf%2Fsqlr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgandaldf%2Fsqlr/lists"}