{"id":19095682,"url":"https://github.com/insei/gerpo","last_synced_at":"2026-04-21T02:01:01.287Z","repository":{"id":257821435,"uuid":"864147820","full_name":"Insei/gerpo","owner":"Insei","description":"GERPO is a generic repository pattern implementation with language integrated query support. Works with any sql sources.","archived":false,"fork":false,"pushed_at":"2026-04-18T18:37:54.000Z","size":369,"stargazers_count":2,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-18T20:28:44.656Z","etag":null,"topics":["cache","generic","go","golang","query-builder","repository","sql","struct-mapping"],"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/Insei.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2024-09-27T15:24:59.000Z","updated_at":"2026-04-18T18:38:00.000Z","dependencies_parsed_at":"2024-10-11T07:54:54.588Z","dependency_job_id":"5d51e1a8-21a5-40e2-bf5a-3d7005c3e671","html_url":"https://github.com/Insei/gerpo","commit_stats":null,"previous_names":["insei/gerpo"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/Insei/gerpo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Insei%2Fgerpo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Insei%2Fgerpo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Insei%2Fgerpo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Insei%2Fgerpo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Insei","download_url":"https://codeload.github.com/Insei/gerpo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Insei%2Fgerpo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32073496,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-21T01:35:38.224Z","status":"online","status_checked_at":"2026-04-21T02:00:06.111Z","response_time":128,"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":["cache","generic","go","golang","query-builder","repository","sql","struct-mapping"],"created_at":"2024-11-09T03:34:46.225Z","updated_at":"2026-04-21T02:01:01.281Z","avatar_url":"https://github.com/Insei.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GERPO\n\n[![codecov](https://codecov.io/gh/Insei/gerpo/graph/badge.svg?token=LGY9O9OJF5)](https://codecov.io/gh/Insei/gerpo)\n[![build](https://github.com/Insei/gerpo/actions/workflows/go.yml/badge.svg)](https://github.com/Insei/gerpo/actions/workflows/go.yml)\n[![Goreport](https://goreportcard.com/badge/github.com/insei/gerpo)](https://goreportcard.com/report/github.com/insei/gerpo)\n[![GoDoc](https://godoc.org/github.com/insei/gerpo?status.svg)](https://godoc.org/github.com/insei/gerpo)\n[![Docs](https://img.shields.io/badge/docs-insei.github.io%2Fgerpo-blue)](https://insei.github.io/gerpo/)\n\n**GERPO** (Golang + Repository) is a generic repository pattern for Go with pluggable adapters and a tiny footprint. It is **not an ORM** — no migrations, no relations, no struct tags. All SQL behavior is declared once in the repository configuration; columns are bound to struct fields through pointers.\n\n\u003e **Database support.** gerpo currently targets **PostgreSQL** (and PG-compatible databases such as CockroachDB). The SQL fragments gerpo emits — placeholder format, LIKE type-casts, `RETURNING`, window functions — assume PostgreSQL. MySQL, MS SQL Server, and pre-3.35 SQLite are **not supported** today. See [`TODO.md`](TODO.md) for the multi-dialect backlog.\n\n\u003e 📚 Full documentation: **[insei.github.io/gerpo](https://insei.github.io/gerpo/)** · [Why gerpo?](https://insei.github.io/gerpo/why-gerpo/) (vs GORM / ent / bun / sqlc / sqlx) · API reference: **[pkg.go.dev/github.com/insei/gerpo](https://pkg.go.dev/github.com/insei/gerpo)**\n\n## Install\n\n```bash\ngo get github.com/insei/gerpo@latest\n```\n\nMinimum Go version: **1.24**.\n\n## Quick start\n\n```go\ntype User struct {\n    ID        uuid.UUID\n    Name      string\n    Email     *string\n    Age       int\n    CreatedAt time.Time\n}\n\nrepo, err := gerpo.New[User]().\n    Adapter(pgx5.NewPoolAdapter(pool)).\n    Table(\"users\").\n    Columns(func(m *User, c *gerpo.ColumnBuilder[User]) {\n        c.Field(\u0026m.ID).OmitOnUpdate()\n        c.Field(\u0026m.Name)\n        c.Field(\u0026m.Email)\n        c.Field(\u0026m.Age)\n        c.Field(\u0026m.CreatedAt).OmitOnUpdate()\n    }).\n    Build()\n\nusers, _ := repo.GetList(ctx, func(m *User, h query.GetListHelper[User]) {\n    h.Where().Field(\u0026m.Age).GTE(18)\n    h.OrderBy().Field(\u0026m.CreatedAt).DESC()\n    h.Page(1).Size(20)\n})\n```\n\nFull runnable sample lives in [`examples/todo-api/`](examples/todo-api/) — a CRUD REST service with PostgreSQL, goose migrations and docker-compose wiring. Additional end-to-end scenarios are in the [integration tests](tests/integration/).\n\n## Features\n\n| Area | Highlights | Docs |\n|---|---|---|\n| Repository | Type-safe builder, thread-safe, `sync.Pool` backed statements | [Repository builder](https://insei.github.io/gerpo/features/repository/) |\n| Columns | `AsColumn` / `AsVirtual`, insert/update protection, aliases | [Columns](https://insei.github.io/gerpo/features/columns/), [Virtual columns](https://insei.github.io/gerpo/features/virtual-columns/) |\n| Queries | 14 WHERE operators + IC variants, AND/OR/Group, ordering, pagination | [WHERE operators](https://insei.github.io/gerpo/features/where/), [Ordering \u0026 pagination](https://insei.github.io/gerpo/features/order-pagination/) |\n| Operations | GetFirst / GetList / Count / Insert / InsertMany / Update / Delete with `Only` / `Exclude` | [CRUD operations](https://insei.github.io/gerpo/features/crud/), [Exclude \u0026 Only](https://insei.github.io/gerpo/features/exclude-only/) |\n| Persistent queries | Always-on WHERE, JOIN, GROUP BY via `WithQuery` | [Persistent queries](https://insei.github.io/gerpo/features/persistent-queries/) |\n| Soft delete | Rewrite DELETE as UPDATE of a marker field | [Soft delete](https://insei.github.io/gerpo/features/soft-delete/) |\n| Hooks | Before/After for Insert/Update, AfterSelect | [Hooks](https://insei.github.io/gerpo/features/hooks/) |\n| Transactions | `gerpo.WithTx(ctx, tx)` / `gerpo.RunInTx` share one tx across every Repository bound to the same context | [Transactions](https://insei.github.io/gerpo/features/transactions/) |\n| Cache | Context-scoped cache out of the box, pluggable backend | [Cache](https://insei.github.io/gerpo/features/cache/) |\n| Error handling | `WithErrorTransformer` maps gerpo errors to domain errors | [Error transformer](https://insei.github.io/gerpo/features/error-transformer/) |\n\n## Supported adapters\n\ngerpo talks to a database through an `executor.Adapter` — a thin wrapper around an underlying SQL driver. gerpo targets **PostgreSQL** today; all three bundled adapters wrap PostgreSQL drivers:\n\n| Adapter | Package | Wraps driver | Placeholders |\n|---|---|---|---|\n| pgx v5 | `executor/adapters/pgx5` | `github.com/jackc/pgx/v5` | `$1, $2, …` |\n| pgx v4 | `executor/adapters/pgx4` | `github.com/jackc/pgx/v4` | `$1, $2, …` |\n| database/sql | `executor/adapters/databasesql` | any `database/sql` driver — pair with a PG driver (`pq`, `pgx/stdlib`) | `?` or `$1` (configurable) |\n\nPG-compatible databases (CockroachDB, MariaDB ≥10.5, SQLite ≥3.35) are likely to work as drop-in — not formally tested. MySQL, MS SQL Server, and older SQLite are **not supported**: gerpo's LIKE `CAST(? AS text)`, `INSERT … RETURNING`, and window-function `COUNT(*) OVER ()` all assume PG. See [`TODO.md`](TODO.md).\n\nWriting a custom adapter is three methods (`ExecContext`, `QueryContext`, `BeginTx`) — see [Adapters](https://insei.github.io/gerpo/features/adapters/) and [adapter internals](https://insei.github.io/gerpo/architecture/adapters-internals/).\n\n## Ideology\n\n1. SQL lives only in the repository configuration.\n2. Columns are bound to struct fields through pointers.\n3. Entities carry no database markers (no tags, no interfaces).\n4. gerpo does not implement relations between entities.\n5. gerpo does not modify the database schema.\n\nDetails and rationale: [Ideology](https://insei.github.io/gerpo/architecture/ideology/).\n\n## Performance\n\ngerpo uses minimal reflection and pools statement objects to keep allocations under control. Two views of the overhead — a mock adapter isolates the framework cost, a real PostgreSQL shows the cost a caller actually experiences with network round-trip in the picture.\n\n**Against real PostgreSQL.** `make bench-report-pg` spins up an isolated `postgres:16` in Docker, applies the bench schema, runs every CRUD op paired (pgx v5 pool vs gerpo repo), and tears the stack down. Sample run on a local machine:\n\n| Op        | Direct ns/op | Gerpo ns/op | × ns | × B  | × allocs |\n|-----------|-------------:|------------:|-----:|-----:|---------:|\n| GetFirst  | 59 804       | 66 878      | 1.1× | 2.0× | 1.5×     |\n| GetList   | 84 030       | 100 375     | 1.2× | 1.2× | 1.1×     |\n| Count     | 105 780      | 162 432     | 1.5× | 2.6× | 2.9×     |\n| Insert    | 1 607 957    | 1 638 373   | 1.0× | 2.4× | 2.0×     |\n| Update    | 1 488 061    | 1 621 205   | 1.1× | 3.1× | 2.6×     |\n| Delete    | 58 162       | 63 522      | 1.1× | 2.3× | 2.0×     |\n\nReads and Delete-on-miss come out at roughly +10 % latency. `INSERT` / `UPDATE` sit at ~1.6 ms per call on a local PG — that is a real fsync on commit, not framework overhead; the gerpo layer contributes ~30 µs on top. `Count` is the outlier at +50 % because a trivial `SELECT count(*) WHERE age \u003e= ?` is so cheap that gerpo's fixed per-call cost is visible as a percentage; it shrinks on non-trivial queries. Allocation ratios reflect the price of generic SQL generation and struct-field mapping.\n\n**Against a mock adapter** (IO = 0, `make bench-report`) the ratios are larger — the framework cost is no longer amortised by network. Per-op absolute cost stays in the 0.5–1.5 µs band, which is what survives on real traffic.\n\n## Static analysis — gerpolint\n\nWHERE operators (`EQ`, `In`, `Contains`, …) take `any`, so the compiler cannot\ncatch `h.Where().Field(\u0026m.Age).EQ(\"18\")` — field is `int`, argument is a\nstring — until runtime. gerpo ships a `go/analysis` checker that catches\nthese mismatches at `go vet` time.\n\n```bash\ngo install github.com/insei/gerpo/cmd/gerpolint@latest\ngerpolint ./...\n# …or from a clone:\nmake lint-gerpolint\n```\n\nRules (`GPL001`..`GPL005`): scalar type mismatch, variadic element mismatch,\nstring-only operator on non-string field, unresolved field pointer, and\n`any`-typed argument. Silence specific lines with `//gerpolint:disable-line`,\n`//gerpolint:disable-next-line[=GPL001,…]`, or the\n`//gerpolint:disable` / `//gerpolint:enable` block pair.\n\n**Using gerpolint as a golangci-lint plugin.** Drop the repo's\n[`.custom-gcl.yml`](.custom-gcl.yml) into your project (pointing\n`module: github.com/insei/gerpo`, `import: github.com/insei/gerpo/gerpolintplugin`),\nadd gerpolint to your linters config, and build a bespoke binary:\n\n```bash\ngolangci-lint custom         # produces ./bin/custom-gcl with gerpolint embedded\n./bin/custom-gcl run ./...\n```\n\n```yaml\n# .golangci.yml\nlinters:\n  enable: [gerpolint]\n  settings:\n    custom:\n      gerpolint:\n        type: module\n        settings:\n          unresolved-field: skip   # skip | warn | error\n          any-arg: warn            # skip | warn | error\n          disabled-rules: []       # [GPL001, GPL002, …]\n```\n\n## Roadmap\n\n**1.0.0**\n\n- [x] Caching engine configuration in the repository builder (#46).\n- [x] New API for configuring virtual columns — `Compute(sql, args...)` replaced `WithSQL`; `Aggregate()` marks aggregate expressions; `Filter(op, spec)` registers per-operator overrides.\n\nThe API is now stable and ready for v1.0.0.\n\n## Contributing\n\n- Unit tests: `go test ./...`\n- Integration tests (Docker required):\n\n  ```bash\n  docker compose -f tests/integration/docker-compose.yml up -d\n  GERPO_INTEGRATION_DB_URL=\"postgres://gerpo:gerpo@localhost:5433/gerpo?sslmode=disable\" \\\n      go test -tags=integration ./tests/integration/...\n  ```\n\n- Every PR runs a mock-db benchmark diff via `benchstat` and posts the summary as a PR comment.\n\nMore in [Contributing](https://insei.github.io/gerpo/architecture/contributing/).\n\n## License\n\nMIT — see [LICENSE.md](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finsei%2Fgerpo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finsei%2Fgerpo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finsei%2Fgerpo/lists"}