{"id":45640866,"url":"https://github.com/mutablelogic/go-pg","last_synced_at":"2026-04-16T10:01:15.466Z","repository":{"id":275727411,"uuid":"926977035","full_name":"mutablelogic/go-pg","owner":"mutablelogic","description":"Postgresql Support for golang","archived":false,"fork":false,"pushed_at":"2026-04-05T12:43:58.000Z","size":34817,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-05T14:32:40.120Z","etag":null,"topics":["pgx","postgres","postgresql"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/mutablelogic/go-pg","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mutablelogic.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-02-04T07:29:21.000Z","updated_at":"2026-04-05T12:43:31.000Z","dependencies_parsed_at":"2025-02-04T09:24:40.780Z","dependency_job_id":"8788d274-1d14-45e2-8c42-38840dc3cfa2","html_url":"https://github.com/mutablelogic/go-pg","commit_stats":null,"previous_names":["djthorpe/go-pg"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/mutablelogic/go-pg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-pg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-pg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-pg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-pg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mutablelogic","download_url":"https://codeload.github.com/mutablelogic/go-pg/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutablelogic%2Fgo-pg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31880882,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T09:23:21.276Z","status":"ssl_error","status_checked_at":"2026-04-16T09:23:15.028Z","response_time":69,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["pgx","postgres","postgresql"],"created_at":"2026-02-24T02:32:12.150Z","updated_at":"2026-04-16T10:01:15.393Z","avatar_url":"https://github.com/mutablelogic.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-pg\n\nPostgresql Support for Go, built on top of [pgx](https://github.com/jackc/pgx). This module provides:\n\n* Binding SQL statements to named arguments;\n* Support for mapping go structures to SQL tables, and vice versa;\n* Easy semantics for Insert, Delete, Update, Get and List operations;\n* Bulk insert operations and transactions;\n* Support for queries stored in external files with named parameters to improve separation of concerns;\n* Support for tracing and observability;\n* [PostgreSQL Manager](pkg/manager/README.md) for server administration with REST API, an optional frontend and prometheus metrics;\n* [Task Queue](pkg/queue/README.md) for task queue (and interval task) processing using PostgreSQL as a backend;\n* [Testing utilities](pkg/test/README.md) for integration testing with testcontainers.\n\nDocumentation: \u003chttps://pkg.go.dev/github.com/mutablelogic/go-pg\u003e\n\n## Motivation\n\nThe package provides a simple way to interact with a Postgresql database from Go, to reduce\nthe amount of boilerplate code required to interact with the database. The supported operations\nalign with API calls _POST_, _PUT_, _GET_, _DELETE_ and _PATCH_.\n\n* **Insert** - Insert a row into a table, and return the inserted row (_POST_ or _PUT_);\n* **Delete** - Delete one or more rows from a table, and optionally return the deleted rows (_DELETE_);\n* **Update** - Update one or more rows in a table, and optionally return the updated rows (_PATCH_);\n* **Get** - Get a single row from a table (_GET_);\n* **List** - Get a list of rows from a table (_GET_).\n\nIn order to support database operations on go types, those types need to implement one or\nmore of the following interfaces:\n\n### Selector\n\n```go\ntype Selector interface {\n  // Bind row selection variables, returning the SQL statement required for the operation\n  // The operation can be Get, Update, Delete or List\n  Select(*Bind, Op) (string, error)\n}\n```\n\nA type which implements a `Selector` interface can be used to select rows from a table, for get, list, update\nand deleting operations.\n\n### Reader\n\n```go\ntype Reader interface {\n  // Scan a row into the receiver\n  Scan(Row) error\n}\n```\n\nA type which implements a `Reader` interface can be used to translate SQL types to the type instance. If multiple\nrows are returned, then the `Scan` method is called repeatly until no more rows are returned.\n\n```go\ntype ListReader interface {\n  // Scan a count of returned rows into the receiver\n  ScanCount(Row) error\n}\n```\n\nA type which implements a `ListReader` interface can be used to scan the count of rows returned.\n\n### Writer\n\n```go\ntype Writer interface {\n  // Bind insert parameters, returning the SQL statement required for the insert\n  Insert(*Bind) (string, error)\n\n  // Bind update parameters\n  Update(*Bind) error\n}\n```\n\nA type which implements a `Writer` interface can be used to bind object instance variables to SQL parameters.\nAn example of how to implement an API gateway using this package is shown below.\n\n## Database Server Connection Pool\n\nYou can create a connection pool to a database server using the `pg.NewPool` function:\n\n```go\nimport (\n  pg \"github.com/mutablelogic/go-pg\"\n)\n\nfunc main() {\n  pool, err := pg.NewPool(ctx,\n    pg.WithHostPort(host, port),\n    pg.WithCredentials(\"postgres\", \"password\"),\n    pg.WithDatabase(name),\n  )\n  if err != nil {\n      panic(err)\n  }\n  defer pool.Close()\n\n  // ...\n}\n```\n\nThe options that can be passed to `pg.NewPool` are:\n\n* `pg.WithURL(string)` - Set connection parameters from a PostgreSQL URL in the format\n  `postgres://user:password@host:port/database?sslmode=disable`. Query parameters are\n  passed as additional connection options.\n* `pg.WithCredentials(string, string)` - Set connection pool username and password.\n  If the database name is not set, then the username will be used as the default database name.\n* `pg.WithDatabase(string)` - Set the database name for the connection. If the user name is not set,\n  then the database name will be used as the user name.\n* `pg.WithAddr(string)` - Set the address (host) or (host:port) for the connection.\n* `pg.WithHostPort(string, string)` - Set the hostname and port for the connection. If the port\n  is not set, then the default port 5432 will be used.\n* `pg.WithSSLMode(string)` - Set the SSL connection mode. Valid values are \"disable\", \"allow\",\n  \"prefer\", \"require\", \"verify-ca\", \"verify-full\". See\n  \u003chttps://www.postgresql.org/docs/current/libpq-ssl.html\u003e for more information.\n* `pg.WithApplicationName(string)` - Set the application name for the connection.\n  This appears in `pg_stat_activity` and helps identify connections.\n* `pg.WithSchemaSearchPath(string...)` - Set the schema search path for the connection.\n* `pg.WithTrace(pg.TraceFn)` - Set the trace function for the connection pool.\n  The signature is `func(ctx context.Context, sql string, args any, err error)`.\n* `pg.WithTracer(trace.Tracer)` - Set an OpenTelemetry tracer for the connection pool.\n  Spans are created for every query executed through the pool.\n* `pg.WithBind(string, any)` - Set a bind variable for the lifetime of the connection.\n* `pg.WithPassword(string)` - Override the connection password without affecting the username\n  or database name. Useful when the password is supplied separately from the URL.\n\n## Executing Statements\n\nTo simply execute a statement, use the `Exec` call:\n\n```go\n  if err := pool.Exec(ctx, `CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT)`); err != nil {\n    panic(err)\n  }\n```\n\nYou can use `bind variables` to bind named arguments to a statement using the `With` function.\nWithin the statement, the following formats are replaced with bound values:\n\n* `${\"name\"}` - Replace with the value of the named argument \"name\", double-quoted string\n* `${'name'}` - Replace with the value of the named argument \"name\", single-quoted string\n* `${name}` - Replace with the value of the named argument \"name\", unquoted string\n* `$$` - Pass a literal dollar sign\n* `@name` - Pass by bound variable parameter\n\nFor example,\n\n```go\n  var name string\n  // ...\n  if err := pool.With(\"table\", \"test\", \"name\", name).Exec(ctx, `INSERT INTO ${\"table\"} (name) VALUES (@name)`); err != nil {\n    panic(err)\n  }\n```\n\nThis will re-use or create a new database connection from the connection, pool, bind the named arguments, replace\nthe named arguments in the statement, and execute the statement.\n\n## Implementing Get\n\nIf you have a http handler which needs to get a row from a table, you can implement a `Selector` interface.\nFor example,\n\n```go\ntype MyObject struct {\n  Id int\n  Name string\n}\n\n// Reader - bind to object\nfunc (obj *MyObject) Scan(row pg.Row) error {\n  return row.Scan(\u0026obj.Id, \u0026obj.Name)\n}\n\n// Selector - select rows from database\nfunc (obj MyObject) Select(bind *pg.Bind, op pg.Op) (string, error) {\n  switch op {\n  case pg.Get:\n    bind.Set(\"id\", obj.Id)\n    return `SELECT id, name FROM mytable WHERE id=@id`, nil\n  }\n}\n\n// Select the row from the database\nfunc main() {\n  // ...\n  var obj MyObject\n  if err := conn.Get(ctx, \u0026obj, MyObject{ Id: 1 }); err != nil {\n    panic(err)\n  }\n  // ...\n}\n```\n\n## Implementing List\n\nYou may wish to use paging to list rows from a table. The `List` operation is used to\nlist rows from a table, with offset and limit parameters.\nThe http handler may look like this:\n\n```go\nfunc ListHandler(w http.ResponseWriter, r *http.Request) {\n  var conn pg.Conn\n\n  // ....Set pool....\n\n  // Get up to 10 rows\n  var response MyList\n  if err := conn.List(ctx, \u0026response, MyListRequest{Offset: 0, Limit: 10}); err != nil {\n    http.Error(w, err.Error(), http.StatusInternalServerError)\n    return\n  }\n\n  // Write the row to the response - TODO: Add Content-Type header\n  json.NewEncoder(w).Encode(response)\n}\n\n```\n\nThe implementation of MyList and MyListRequest may look like this:\n\n```go\ntype MyListRequest struct {\n  Offset uint64\n  Limit uint64\n}\n\ntype MyList struct {\n  Count uint64\n  Names []string\n}\n\n// Reader - note this needs to be a pointer receiver\nfunc (obj *MyList) Scan(row pg.Row) error {\n  var name string\n  if err := row.Scan(\u0026name); err != nil {\n    return err\n  }\n  obj.Names = append(obj.Names, name)\n  return nil\n}\n\n// ListReader - optional interface to scan count of all rows\nfunc (obj *MyList) ScanCount(row pg.Row) error {\n  return row.Scan(\u0026obj.Count)\n}\n\n// Selector - select rows from database. Use bind variables\n// offsetlimit, groupby and orderby to filter the selected rows.\nfunc (obj MyListRequest) Select(bind *pg.Bind, op pg.Op) (string, error) {\n  bind.Set(\"offsetlimit\", fmt.Sprintf(\"OFFSET %v LIMIT %v\",obj.Offset,obj.Limit))\n  switch op {\n  case pg.List:\n    return `SELECT name FROM mytable`, nil\n  default:\n    return \"\", fmt.Errorf(\"unsupported operation: %v\", op)\n  }\n}\n```\n\nYou can of course use a `WHERE` clause in your query to filter the rows returned from\nthe table. Always implement the `offsetlimit` as a bind variable.\n\n## Implementing Insert\n\nTo insert a row into a table, implement the `Writer` interface:\n\n```go\ntype MyObject struct {\n  Id   int\n  Name string\n}\n\n// Writer - bind insert parameters\nfunc (obj MyObject) Insert(bind *pg.Bind) (string, error) {\n  bind.Set(\"name\", obj.Name)\n  return `INSERT INTO mytable (name) VALUES (@name) RETURNING id, name`, nil\n}\n\n// Reader - scan the returned row\nfunc (obj *MyObject) Scan(row pg.Row) error {\n  return row.Scan(\u0026obj.Id, \u0026obj.Name)\n}\n\n// Insert a row\nfunc main() {\n  // ...\n  obj := MyObject{Name: \"hello\"}\n  if err := conn.Insert(ctx, \u0026obj, obj); err != nil {\n    panic(err)\n  }\n  fmt.Println(\"Inserted with ID:\", obj.Id)\n}\n```\n\nThe `RETURNING` clause allows you to get the inserted row back, including any auto-generated values like serial IDs.\n\n## Implementing Patch\n\nTo update rows in a table, implement both `Selector` (to identify rows) and `Writer` (for update values):\n\n```go\ntype MyObject struct {\n  Id   int\n  Name string\n}\n\n// Selector - identify rows to update\nfunc (obj MyObject) Select(bind *pg.Bind, op pg.Op) (string, error) {\n  switch op {\n  case pg.Update:\n    bind.Set(\"id\", obj.Id)\n    return `UPDATE mytable SET name = @name WHERE id = @id RETURNING id, name`, nil\n  }\n  return \"\", pg.ErrNotImplemented\n}\n\n// Writer - bind update parameters\nfunc (obj MyObject) Update(bind *pg.Bind) error {\n  bind.Set(\"name\", obj.Name)\n  return nil\n}\n\n// Reader - scan the returned row\nfunc (obj *MyObject) Scan(row pg.Row) error {\n  return row.Scan(\u0026obj.Id, \u0026obj.Name)\n}\n\n// Update a row\nfunc main() {\n  // ...\n  obj := MyObject{Id: 1, Name: \"updated\"}\n  if err := conn.Update(ctx, \u0026obj, obj, obj); err != nil {\n    panic(err)\n  }\n}\n```\n\n## Implementing Delete\n\nTo delete rows from a table, implement the `Selector` interface:\n\n```go\ntype MyObject struct {\n  Id   int\n  Name string\n}\n\n// Selector - identify rows to delete\nfunc (obj MyObject) Select(bind *pg.Bind, op pg.Op) (string, error) {\n  switch op {\n  case pg.Delete:\n    bind.Set(\"id\", obj.Id)\n    return `DELETE FROM mytable WHERE id = @id RETURNING id, name`, nil\n  }\n  return \"\", pg.ErrNotImplemented\n}\n\n// Reader - optionally scan deleted rows\nfunc (obj *MyObject) Scan(row pg.Row) error {\n  return row.Scan(\u0026obj.Id, \u0026obj.Name)\n}\n\n// Delete a row\nfunc main() {\n  // ...\n  var deleted MyObject\n  if err := conn.Delete(ctx, \u0026deleted, MyObject{Id: 1}); err != nil {\n    panic(err)\n  }\n  fmt.Println(\"Deleted:\", deleted.Name)\n}\n```\n\nThe `RETURNING` clause is optional but useful for confirming what was deleted.\n\n## Transactions\n\nTransactions are executed within a function called `Tx`. For example,\n\n```go\n  if err := pool.Tx(ctx, func(tx pg.Conn) error {\n    if err := tx.Exec(ctx, `CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT)`); err != nil {\n      return err\n    }\n    if err := tx.Exec(ctx, `INSERT INTO test (name) VALUES ('hello')`); err != nil {\n      return err\n    }\n    return nil\n  }); err != nil {\n    panic(err)\n  }\n```\n\nAny error returned from the function will cause the transaction to be rolled back. If the function returns `nil`, then\nthe transaction will be committed. Transactions can be nested.\n\n## Notify and Listen\n\nPostgreSQL supports asynchronous notifications via `NOTIFY` and `LISTEN`. The preferred API is `Subscribe`, which acquires a dedicated listener connection from the pool, returns a notification channel, and automatically unsubscribes when the context is cancelled or the pool is closed.\n\n```go\nimport (\n  \"context\"\n  \"fmt\"\n\n  pg \"github.com/mutablelogic/go-pg\"\n)\n\n// Subscribe to a channel using a pool-backed connection.\nnotifications, err := pool.Subscribe(ctx, \"my_channel\")\nif err != nil {\n  panic(err)\n}\n\nfor {\n  select {\n  case \u003c-ctx.Done():\n    return\n  case n, ok := \u003c-notifications:\n    if !ok {\n      return\n    }\n    fmt.Printf(\"Channel: %s, Payload: %s\\n\", n.Channel, n.Payload)\n  }\n}\n```\n\nSubscriptions are long-lived and tied to a dedicated PostgreSQL session, so they are only supported on pool-backed connections. Calling `Subscribe` from a transactional or bulk connection returns `pg.ErrNotAvailable`.\n\n`Subscribe` returns setup errors only. After registration, the subscription runs in the background and closes the returned channel if the context is cancelled or if the listener encounters an error such as a dropped connection.\n\nWhen `pool.Close()` is called, all active subscriptions are cancelled and the pool waits for their listener goroutines to exit and channels to close before returning.\n\nTo send a notification from another connection:\n\n```go\nif err := pool.Exec(ctx, `NOTIFY my_channel, 'hello world'`); err != nil {\n  panic(err)\n}\n```\n\nThe lower-level `Listener` API still exists for compatibility, but `Subscribe` is the recommended interface for new code.\n\n## Schema Support\n\nThe package provides convenience functions for managing PostgreSQL schemas:\n\n```go\nimport pg \"github.com/mutablelogic/go-pg\"\n\n// Check if a schema exists\nexists, err := pg.SchemaExists(ctx, conn, \"myschema\")\n\n// Create a schema (IF NOT EXISTS)\nerr := pg.SchemaCreate(ctx, conn, \"myschema\")\n\n// Drop a schema (IF EXISTS, CASCADE)\nerr := pg.SchemaDrop(ctx, conn, \"myschema\")\n```\n\n## Error Handling and Tracing\n\nThe package provides typed errors for common PostgreSQL conditions. Most query helpers already\nnormalize driver errors before returning them, but `pg.NormalizeError` is available if you need to\nnormalize a raw `pgx` or `pgconn` error yourself.\n\nCommon checks are:\n\n* `pg.ErrNotFound` for `pgx.ErrNoRows`\n* `pg.ErrConflict` and `pg.ErrUniqueViolation` for SQLSTATE `23505`\n* `pg.ErrBadParameter` for common input and constraint errors such as foreign key, not null,\n  check constraint and invalid text/date formats\n* `pg.ErrDatabase` for any PostgreSQL error with a SQLSTATE code\n\n```go\nimport (\n  \"errors\"\n  \"log\"\n\n  pg \"github.com/mutablelogic/go-pg\"\n)\n\nerr := conn.Update(ctx, \u0026obj, req, req)\nerr = pg.NormalizeError(err)\n\nswitch {\ncase errors.Is(err, pg.ErrNotFound):\n  // Row not found\ncase errors.Is(err, pg.ErrConflict):\n  // Duplicate key or other conflict\ncase errors.Is(err, pg.ErrBadParameter):\n  // Invalid user-supplied data\ncase errors.Is(err, pg.ErrDatabase):\n  // Other PostgreSQL error\n  log.Printf(\"sqlstate=%s err=%v\", pg.SQLState(err), err)\ncase err != nil:\n  // Non-database error\n}\n```\n\nIf you need the specific PostgreSQL code for logging or translation, use `pg.SQLState(err)`.\n\n```go\nimport pg \"github.com/mutablelogic/go-pg\"\n\nif err := conn.Get(ctx, \u0026obj, req); err != nil {\n  if errors.Is(err, pg.ErrNotFound) {\n    // Row not found\n  } else if errors.Is(err, pg.ErrConflict) {\n    // Conflict\n  } else if errors.Is(err, pg.ErrBadParameter) {\n    // Invalid parameter\n  } else if errors.Is(err, pg.ErrDatabase) {\n    log.Printf(\"postgres error: sqlstate=%s err=%v\", pg.SQLState(err), err)\n  } else {\n    // Other error\n  }\n}\n```\n\nTo enable query tracing, pass a trace function when creating the pool:\n\n```go\npool, err := pg.NewPool(ctx,\n  pg.WithHostPort(host, port),\n  pg.WithCredentials(user, pass),\n  pg.WithTrace(func(ctx context.Context, sql string, args any, err error) {\n    if err != nil {\n      log.Printf(\"ERROR: %s: %v\", sql, err)\n    } else {\n      log.Printf(\"SQL: %s args=%v\", sql, args)\n    }\n  }),\n)\n```\n\nThe trace function is called for every query executed through the connection pool.\n\n## Testing Support\n\nThe `pkg/test` package provides utilities for integration testing with PostgreSQL using testcontainers.\n\nSee [pkg/test/README.md](pkg/test/README.md) for documentation.\n\n## PostgreSQL Manager\n\nSee [pkg/manager/README.md](pkg/manager/README.md) for documentation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutablelogic%2Fgo-pg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmutablelogic%2Fgo-pg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutablelogic%2Fgo-pg/lists"}