{"id":18026836,"url":"https://github.com/yegor256/pgtk","last_synced_at":"2026-04-19T13:05:01.424Z","repository":{"id":56887941,"uuid":"181332320","full_name":"yegor256/pgtk","owner":"yegor256","description":"PostgreSQL ToolKit for Ruby apps: Liquibase + Rake + Connection Pool","archived":false,"fork":false,"pushed_at":"2026-04-14T04:44:50.000Z","size":830,"stargazers_count":7,"open_issues_count":6,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-04-14T06:28:25.776Z","etag":null,"topics":["connection-pool","liquibase","postgresql","rake-task","rakefile","ruby"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/pgtk","language":"Ruby","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/yegor256.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2019-04-14T15:49:45.000Z","updated_at":"2026-04-14T04:44:54.000Z","dependencies_parsed_at":"2024-01-16T08:53:24.292Z","dependency_job_id":"6b42b129-9122-4a5a-86e3-6b600b84883d","html_url":"https://github.com/yegor256/pgtk","commit_stats":{"total_commits":45,"total_committers":3,"mean_commits":15.0,"dds":0.0444444444444444,"last_synced_commit":"a74b7cb5fd61ca46c45008d108460d096a23ace8"},"previous_names":[],"tags_count":82,"template":false,"template_full_name":null,"purl":"pkg:github/yegor256/pgtk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fpgtk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fpgtk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fpgtk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fpgtk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yegor256","download_url":"https://codeload.github.com/yegor256/pgtk/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yegor256%2Fpgtk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32005898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"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":["connection-pool","liquibase","postgresql","rake-task","rakefile","ruby"],"created_at":"2024-10-30T08:08:14.170Z","updated_at":"2026-04-19T13:05:01.398Z","avatar_url":"https://github.com/yegor256.png","language":"Ruby","readme":"# Ruby + PostgreSQL + Liquibase + Rake\n\n[![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org)\n[![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/pgtk)](https://www.rultor.com/p/yegor256/pgtk)\n[![We recommend RubyMine](https://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/)\n\n[![rake](https://github.com/yegor256/pgtk/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/pgtk/actions/workflows/rake.yml)\n[![PDD status](https://www.0pdd.com/svg?name=yegor256/pgtk)](https://www.0pdd.com/p?name=yegor256/pgtk)\n[![Gem Version](https://badge.fury.io/rb/pgtk.svg)](https://badge.fury.io/rb/pgtk)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/pgtk/blob/master/LICENSE.txt)\n[![Test Coverage](https://img.shields.io/codecov/c/github/yegor256/pgtk.svg)](https://codecov.io/github/yegor256/pgtk?branch=master)\n[![Hits-of-Code](https://hitsofcode.com/github/yegor256/pgtk)](https://hitsofcode.com/view/github/yegor256/pgtk)\n\nThis small Ruby gem helps you integrate\n[PostgreSQL](https://www.postgresql.org/) with your Ruby\nweb app, through [Liquibase](https://www.liquibase.org/).\nIt also adds a simple connection pool\nand query processor, to make SQL manipulation simpler.\n\nFirst of all, on top of\n[Ruby](https://www.ruby-lang.org/en/) and\n[Bundler](https://bundler.io/)\nyou need to have\n[PostgreSQL](https://www.postgresql.org/),\n[Java 8+](https://java.com/en/download/), and\n[Maven 3.2+](https://maven.apache.org/) installed.\nIn Ubuntu 16+ this should be enough:\n\n```bash\nsudo apt-get install -y postgresql-10 postgresql-client-10\nsudo apt-get install -y default-jre maven\n```\n\nThen, add this to your [Gemfile](https://bundler.io/gemfile.html):\n\n```ruby\ngem 'pgtk'\n```\n\nThen, add this to your\n[Rakefile](https://github.com/ruby/rake/blob/master/doc/rakefile.rdoc):\n\n```ruby\nrequire 'pgtk/pgsql_task'\nPgtk::PgsqlTask.new :pgsql do |t|\n  # Temp directory with PostgreSQL files:\n  t.dir = 'target/pgsql'\n  # To delete the directory on every start;\n  t.fresh_start = true\n  t.user = 'test'\n  t.password = 'test'\n  t.dbname = 'test'\n  # YAML file to be created with connection details:\n  t.yaml = 'target/pgsql-config.yml'\n  # List of contexts or empty if all:\n  t.contexts = '!test'\n  # List of PostgreSQL configuration options:\n  t.config = {\n    log_min_messages: 'ERROR',\n    log_filename: 'target/pg.log'\n  }\nend\n```\n\nAnd this too\n([org.postgresql:postgresql][plugin-1]\nand [org.liquibase:liquibase-maven-plugin][plugin-2]\nare used inside):\n\n```ruby\nrequire 'pgtk/liquibase_task'\nPgtk::LiquibaseTask.new liquibase: :pgsql do |t|\n  # Master XML file path:\n  t.master = 'liquibase/master.xml'\n  # YAML files connection details:\n  t.yaml = ['target/pgsql-config.yml', 'config.yml']\n  # Reduce the amount of log messages (TRUE by default):\n  t.quiet = false\n  # Overwriting default version of PostgreSQL server:\n  t.postgresql_version = '42.7.0'\n  # Overwriting default version of Liquibase:\n  t.liquibase_version = '3.2.2'\nend\n```\n\nThe `config.yml` file should be in this format:\n\n```yaml\npgsql:\n  url: jdbc:postgresql://\u003chost\u003e:\u003cport\u003e/\u003cdbname\u003e?user=\u003cuser\u003e\n  host: ...\n  port: ...\n  dbname: ...\n  user: ...\n  password: ...\n```\n\nYou should create that `liquibase/master.xml` file in your repository,\nand a number of other XML files with Liquibase changes. This\n[example](https://github.com/zold-io/wts.zold.io/tree/master/liquibase)\nwill help you understand them.\n\nNow, you can do this:\n\n```bash\nbundle exec rake pgsql liquibase\n```\n\nA temporary PostgreSQL server will be started and the entire set of\nLiquibase SQL changes will be applied. You will be able to connect\nto it from your application, using the file `target/pgsql-config.yml`.\n\nFrom inside your app you may find this class useful:\n\n```ruby\nrequire 'pgtk/pool'\npgsql = Pgtk::Pool.new(Pgtk::Wire::Yaml.new('config.yml'), max: 5)\npgsql.start! # Start it with five simultaneous connections\n```\n\nYou can also let it pick the connection parameters from the environment\nvariable `DATABASE_URL`, formatted like\n`postgres://user:password@host:5432/dbname`:\n\n```ruby\npgsql = Pgtk::Pool.new(Pgtk::Wire::Env.new)\n```\n\nNow you can fetch some data from the DB:\n\n```ruby\nname = pgsql.exec('SELECT name FROM user WHERE id = $1', [id])[0]['name']\n```\n\nYou may also use it when you need to run a transaction:\n\n```ruby\npgsql.transaction do |t|\n  t.exec('DELETE FROM user WHERE id = $1', [id])\n  t.exec('INSERT INTO user (name, phone) VALUES ($1, $2)', [name, phone])\nend\n```\n\nTo make your PostgreSQL database visible in your unit tests, I would\nrecommend you create a method `test_pgsql` in your `test__helper.rb` file\n(which is `required` in all unit tests) and implement it like this:\n\n```ruby\nrequire 'yaml'\nrequire 'minitest/autorun'\nrequire 'pgtk/pool'\nmodule Minitest\n  class Test\n    def test_pgsql\n      @@test_pgsql ||= Pgtk::Pool.new(\n        Pgtk::Wire::Yaml.new('target/pgsql-config.yml')\n      )\n      @@test_pgsql.start!\n    end\n  end\nend\n```\n\n## Logging with `Pgtk::Spy`\n\nYou can also track all SQL queries sent through the pool,\nwith the help of `Pgtk::Spy`:\n\n```ruby\nrequire 'pgtk/spy'\npool = Pgtk::Spy.new(pool) do |sql|\n  # here, save this \"sql\" somewhere\nend\n```\n\n## Query Timeouts with `Pgtk::Impatient`\n\nTo prevent queries from running indefinitely, use `Pgtk::Impatient` to enforce\ntimeouts on database operations:\n\n```ruby\nrequire 'pgtk/impatient'\n# Wrap the pool with a 5-second timeout for all queries\nimpatient = Pgtk::Impatient.new(pool, 5)\n```\n\nThe impatient decorator ensures queries don't hang your application:\n\n```ruby\nbegin\n  # This query will be terminated if it takes longer than 5 seconds\n  impatient.exec('SELECT * FROM large_table WHERE complex_condition')\nrescue Pgtk::Impatient::TooSlow =\u003e e\n  puts \"Query timed out: #{e.message}\"\nend\n```\n\nYou can exclude specific queries from timeout enforcement using regex patterns:\n\n```ruby\n# Don't timeout any SELECT queries or specific maintenance operations\nimpatient = Pgtk::Impatient.new(pool, 2, /^SELECT/, /^VACUUM/)\n```\n\nKey features:\n\n1. Configurable timeout in seconds for each query\n2. Raises `Pgtk::Impatient::TooSlow` exception when timeout is exceeded\n3. Can exclude queries matching specific patterns from timeout checks\n4. Also sets PostgreSQL's `statement_timeout` for transactions\n\n## Query Caching with `Pgtk::Stash`\n\nFor applications with frequent read queries,\nyou can use `Pgtk::Stash` to add a caching layer:\n\n```ruby\nrequire 'pgtk/stash'\nstash = Pgtk::Stash.new(pgsql)\n```\n\nYou can configure `Stash` with optional parameters:\n\n```ruby\nstash = Pgtk::Stash.new(\n  pgsql,\n  cap: 10_000,          # Maximum cached query results (default: 10,000)\n  cap_interval: 60,     # Seconds between cache size enforcement (default: 60)\n  refill_interval: 16,  # Seconds between stale query refilling (default: 16)\n  refill_delay: 0.5,    # Seconds to wait before refilling the cache\n  retire: 60,           # Maximum age in seconds to keep a query in cache\n  retire_interval: 5.5, # How often to retire (default: 60)\n  threads: 4,           # Worker threads for background refilling (default: 4)\n  max_queue_length: 128 # Maximum refilling tasks in queue (default: 128)\n)\n```\n\n`Stash` automatically caches read queries and invalidates the cache\nwhen tables are modified:\n\n```ruby\n# First execution runs the query against the database\nresult1 = stash.exec('SELECT * FROM users WHERE id = $1', [123])\n# Second execution with the same query and parameters returns cached result\nresult2 = stash.exec('SELECT * FROM users WHERE id = $1', [123])\n# This modifies the 'users' table, invalidating any cached queries for that table\nstash.exec('UPDATE users SET name = $1 WHERE id = $2', ['John', 123])\n# This will execute against the database again since cache was invalidated\nresult3 = stash.exec('SELECT * FROM users WHERE id = $1', [123])\n```\n\nNote that the caching implementation is basic and only suitable\nfor simple queries:\n\n1. Queries must reference tables (using `FROM` or `JOIN`)\n2. Cache is invalidated by table, not by specific rows\n3. Write operations (`INSERT`, `UPDATE`, `DELETE`) bypass\nthe cache and invalidate all cached queries for affected tables\n\n## Automatic Retries with `Pgtk::Retry`\n\nFor resilient database operations, `Pgtk::Retry` provides automatic retry\nfunctionality for failed `SELECT` queries:\n\n```ruby\nrequire 'pgtk/retry'\n# Wrap the pool with retry functionality (default: 3 attempts)\nretry_pool = Pgtk::Retry.new(pgsql)\n# Or specify custom number of attempts\nretry_pool = Pgtk::Retry.new(pgsql, attempts: 5)\n```\n\nThe retry decorator automatically retries `SELECT` queries that fail due to\ntransient errors (network issues, connection problems, etc.):\n\n```ruby\n# This SELECT will be retried up to 3 times if it fails\nusers = retry_pool.exec('SELECT * FROM users WHERE active = true')\n\n# Non-SELECT queries are NOT retried to prevent duplicate writes\nretry_pool.exec('INSERT INTO logs (message) VALUES ($1)', ['User logged in'])\n```\n\nKey features:\n\n1. Only `SELECT` queries are retried (to prevent duplicate data modifications)\n2. Retries happen immediately without delay\n3. The original error is raised after all retry attempts are exhausted\n4. Works seamlessly with other decorators like `Pgtk::Spy` and `Pgtk::Impatient`\n\n## Some Examples\n\nThis library works in\n[netbout.com](https://github.com/yegor256/netbout),\n[wts.zold.io](https://github.com/zold-io/wts.zold.io),\n[mailanes.com](https://github.com/yegor256/mailanes), and\n[0rsk.com](https://github.com/yegor256/0rsk).\n\nThey are all open source, you can see how they use `pgtk`.\n\n## How to contribute\n\nRead\n[these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).\nMake sure your build is green before you contribute\nyour pull request. You will need to have\n[Ruby](https://www.ruby-lang.org/en/) 2.3+ and\n[Bundler](https://bundler.io/) installed. Then:\n\n```bash\nbundle update\nbundle exec rake\n```\n\nIf it's clean and you don't see any error messages, submit your pull request.\n\nTo run a single test, do this:\n\n```bash\nbundle exec ruby test/test_pool.rb -n test_basic\n```\n\n[plugin-1]: https://mvnrepository.com/artifact/org.postgresql/postgresql\n[plugin-2]: https://mvnrepository.com/artifact/org.liquibase/liquibase-maven-plugin\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyegor256%2Fpgtk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyegor256%2Fpgtk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyegor256%2Fpgtk/lists"}