{"id":25268249,"url":"https://github.com/djthorpe/go-pg","last_synced_at":"2025-10-27T06:31:34.189Z","repository":{"id":275727411,"uuid":"926977035","full_name":"djthorpe/go-pg","owner":"djthorpe","description":"Postgresql Support for golang","archived":false,"fork":false,"pushed_at":"2025-02-04T08:53:23.000Z","size":22,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-04T09:24:37.149Z","etag":null,"topics":["pgx","postgres","postgresql"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/djthorpe/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/djthorpe.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}},"created_at":"2025-02-04T07:29:21.000Z","updated_at":"2025-02-04T08:53:26.000Z","dependencies_parsed_at":"2025-02-04T09:24:40.780Z","dependency_job_id":"71c3d42c-24e0-4f57-97f3-6faa5c126c80","html_url":"https://github.com/djthorpe/go-pg","commit_stats":null,"previous_names":["djthorpe/go-pg"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djthorpe%2Fgo-pg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djthorpe%2Fgo-pg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djthorpe%2Fgo-pg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/djthorpe%2Fgo-pg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/djthorpe","download_url":"https://codeload.github.com/djthorpe/go-pg/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238453306,"owners_count":19475049,"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":["pgx","postgres","postgresql"],"created_at":"2025-02-12T10:24:25.282Z","updated_at":"2025-10-27T06:31:34.184Z","avatar_url":"https://github.com/djthorpe.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-pg\n\nPostgresql Support for Go. 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 tracing and observability.\n\nDocumentation: \u003chttps://pkg.go.dev/github.com/djthorpe/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/djthorpe/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* `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* `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* `WithAddr(string)` - Set the address (host) or (host:port) for the connection\n* `WithHostPort(string, string)` - Set the hostname and port for the\n  connection. If the port is not set, then the default port 5432 will be used.\n* `WithSSLMode( string)` - Set the SSL connection mode. Valid values are\n  \"disable\", \"allow\", \"prefer\", \"require\",  \"verify-ca\", \"verify-full\". See\n  \u003chttps://www.postgresql.org/docs/current/libpq-ssl.html\u003e for more information.\n* `pg.WithTrace(pg.TraceFn)` -  Set the trace function for the connection pool.\n  The signature of the trace unction is\n  `func(ctx context.Context, sql string, args any, err error)`\n  and is called for every query executed by the connection pool.\n* `pg.WithBind(string,any)` - Set the bind variable to a value the\n  the lifetime of the connection.\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 = append(obj, row.String())\n  return nil\n}\n\n// ListReader - optional interface to scan count of all rows\nfunc (obj MyList) Scan(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: \",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\nTODO\n\n## Implementing Patch\n\nTODO\n\n## Implementing Delete\n\nTODO\n\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.Tx) 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\nTODO\n\n## Schema Support\n\n* Checking if a schema exists\n* Creating a schema\n* Dropping a schema\n\n## Error Handing and Tracing\n\nTODO\n\n## Testing Support\n\nTODO\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjthorpe%2Fgo-pg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdjthorpe%2Fgo-pg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjthorpe%2Fgo-pg/lists"}