pg: the Ruby client that decides for you between binary and source gems
A PostgreSQL client library for Ruby
At a glance
- What is it?
- pg is the C extension that binds Ruby to PostgreSQL through libpq, and its install path has a real branch in it: a bundled binary gem or a source build against your own libpq. That branch decides which features work.
- Who is it for?
- pg fits Ruby services that talk to PostgreSQL 10 or later and want SQL without an ORM in between, provided you accept the Ruby 3.1 floor and the binary gem's single missing feature, LDAP connection options. It does not fit older PostgreSQL or Ruby deployments, and it is the wrong choice when you need LDAP based connection settings unless you force the source gem.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A direct line from Ruby to PostgreSQL, without an ORM in the middle
pg is the Ruby interface to the PostgreSQL RDBMS. The project states that it works with PostgreSQL 10 and later, and its own requirements list names Ruby 3.1 or newer alongside PostgreSQL 10.x or later. The audience is narrow and specific: Ruby developers who want to open a connection, run SQL, and handle the result set themselves, without an object mapper translating rows into models. The README points you at the PG::Connection class for query methods and the PG::Result class for working with results, which tells you the shape of the API before you write a line.
The smallest example in the project connects and dumps the current connections to the database:
#!/usr/bin/env ruby
require 'pg'
# Output a table of current connections to the DB
conn = PG.connect( dbname: 'sales' )
conn.exec( "SELECT * FROM pg_stat_activity" ) do |result|
puts " PID | User | Query"
result.each do |row|
puts " %7d | %-16s | %s " %
row.values_at('pid', 'usename', 'query')
end
endThat snippet is worth reading as an API sketch. The result is yielded as a block, rows come back as hashes, and the query string is passed straight to the server.
libpq is the real driver, and the C extension sits on top of it
The implementation is a C extension rather than a pure Ruby wrapper. The repository layout makes the split visible: ext/ holds the compiled side, lib/ holds the Ruby layer, and there are separate release build paths for binary gems and source gems in the CI configuration. The gem also carries platform ports under ports/, a gemspec at pg.gemspec, a Rakefile, and platform notes in README-OS_X.rdoc and README-Windows.rdoc.
The practical consequence is that libpq governs what the library can do. The README states that the binary gems have libpq builtin, so they do not depend on the libpq package on the running system, while the source gem needs libpq with headers, which it names as the -dev packages on Linux. Everything the library reports about the server, from parameter status to notices, therefore inherits the behaviour of the libpq it was compiled against, and a binary gem pins that version inside the gem rather than on your system.
Type conversion is where the C layer earns its keep, and the project is explicit about why. It can cast result values and query parameters in Ruby or in native C code, which reduces String allocations and removes conversions from slower Ruby code, according to the README.
Installing pg means choosing the binary gem or forcing the source gem
The default install is one command, and it picks the binary gem for the platform you are running on:
gem install pgIf you need the source build instead, the README gives a two step procedure that uninstalls the binary versions first and then installs for the Ruby platform:
gem uninstall pg --all
gem install pg --platform rubyThat rebuild needs the path to the pg_config program that ships with PostgreSQL when it is not where the build expects it:
gem install pg -- --with-pg-config=<path to pg_config>Two platform details bite in practice. The gems for x86_64-linux and aarch64-linux run on Alpine Linux but require the package gcompat, while the native gems for the *-linux-musl platform work without that dependency. And there is one feature the binary gems do not support at all: retrieval of connection options from LDAP. The README explains that supporting it would require libldap and its many dependencies, and concludes that it is worth supporting the source gem instead.
So the install question is really a feature question. If you rely on LDAP connection options, the source gem is the only path, and that means shipping a build toolchain and libpq headers into your image.
Bundler needs every platform added to the lockfile by hand
Installing pg under Bundler is where the binary gem strategy turns into a chore, because the lockfile only carries the platforms you have told it about. The README shows the commands that add them:
bundle lock --add-platform x86_64-linux
bundle lock --add-platform arm64-darwin
bundle lock --add-platform x64-mingw-ucrt
bundle lock --add-platform ruby
bundle package --all-platformsThe final line, bundle package --all-platforms, is what actually fetches the gems for those platforms, and the README points out that it has to be re-run after a bundle update to pull the newer platform specific gems.
If the binary gems fail on your target for some reason, there is a Gemfile level override:
gem "pg", force_ruby_platform: trueAnd if you need to point the build at a specific pg_config while using Bundler, the compile hints go in the Bundler config rather than on the command line:
bundle config build.pg --with-pg-config=<path to pg_config>Taken together, these four mechanisms form one decision tree: platform in the lockfile, then binary gem, then source gem, then a specific pg_config path. Nobody has to guess which one is in force, as long as you read the lockfile and the Bundler config.
Type casting is two layers, and the C layer does the real work
The type system is the part of pg that goes beyond a thin driver, and the README splits it deliberately into two layers. The lower layer is the encoders and decoders, implemented in C in ext/pg_*coder.c and mirrored in Ruby under lib/pg/, with classes namespaced by format and direction: PG::TextEncoder, PG::TextDecoder, PG::BinaryEncoder and PG::BinaryDecoder. The upper layer is type maps such as PG::BasicTypeMapForResults and PG::BasicTypeMapForQueries.
Switching on the basic maps is a two line change per direction:
conn.type_map_for_results = PG::BasicTypeMapForResults.new conn
conn.exec("select 1, now(), '{2,3}'::int[]").valuesconn.type_map_for_queries = PG::BasicTypeMapForQueries.new conn
conn.exec_params("SELECT $1::text, $2::text, $3::text", [1, 1.23, [2,3]]).valuesThe documented output shows the difference this makes. Without casting, a numeric parameter comes back as the string 1.2300000000000000E+00 and an array as its literal representation; with casting in place the integer arrives as a number and the int array arrives as a Ruby array.
The README also describes the customisation points inside a coder. You can assign a type OID, a format code of text or binary, and optionally a name to an encoder or decoder object, and you can build composite types by assigning an element encoder or decoder to them. A PG::Coder object can then be used either to construct a PG::TypeMap or to convert single values to and from their string representation. The type coverage listed in the README is broad for the common numeric types, with text, binary and both directions available for Integer and Float, binary encoders for Int2, Int4, Int8, Float4 and Float8, and text encoders and decoders for Numeric and Boolean.
The one feature the binary gem drops is LDAP connection options
It is worth stating the gap plainly, because it is the only one the project documents. The binary gems do not support retrieval of connection options from LDAP, and the reason given is a dependency argument rather than a technical impossibility: libldap would be necessary, and it carries a lot of dependencies, so the project judged the feature not widely used enough to be worth supporting in the binary build.
The fallback is deliberate rather than accidental. Forcing the source gem restores LDAP support, at the price of a compile step and libpq headers on the machine doing the build.
Two other details from the install notes matter in CI. The x86_64-linux and aarch64-linux gems run on Alpine Linux but need the gcompat package, while the *-linux-musl native gems work without it. And for signed gems, the public certificates of the gem signers are kept in the certs directory of the repository, so a supply chain check has a documented source. macOS and Windows have their own notes in README-OS_X.rdoc and README-Windows.rdoc rather than in the main document.
Semver versioning, a changelog file and a licence GitHub cannot name
The project tags and releases gems following the Semantic Versioning principle, and it recommends expressing that in a dependency with the pessimistic version constraint at two digits of precision:
spec.add_dependency 'pg', '~> 1.0'That is a sensible default for a library that binds to a server protocol, but it is worth noticing that the GitHub repository has no GitHub releases, so the version history lives in the gem tags and in the CHANGELOG.md file referenced from the README rather than in the release list. The last push to the repository was on 2026-10-19.
The licence is listed as Other, a custom classification GitHub cannot map, and the file BSDL sits at the root of the repository next to LICENSE. Read those files before you redistribute the gem or ship it inside a product, since the README itself does not summarise the terms. The project also keeps older community resources: the README points to a Google+ group and a mailing list for support, and a Japanese translation of the README exists alongside the English one.
One thing the repository does show is breadth. The sample directory contains files named async_api.rb, async_copyto.rb, copydata.rb, cursor.rb, notify_wait.rb, wal_shipper.rb, replication_monitor.rb, lo_sample.rb, array_insert.rb, check_conn.rb and pg_statistics.rb, which at least tells you that asynchronous queries, COPY, cursors, LISTEN and NOTIFY, replication, large objects and array insertion all have worked examples in the tree.
Where pg is the wrong choice
The boundaries are clear enough to state. pg needs PostgreSQL 10 or later and Ruby 3.1 or later, so it is the wrong tool for an older database or an older Ruby runtime, and it is the wrong tool entirely if your datastore is not PostgreSQL, since there is no equivalent driver hiding behind the same API.
It is also the wrong choice when your infrastructure forbids a compile step and you depend on LDAP connection options, because the binary gem path removes exactly that feature. That trade-off is unavoidable rather than a configuration mistake.
There is a subtler reason to look elsewhere: if you want results shaped as domain objects, transactions managed by a framework, or connection pooling with a policy attached, pg deliberately stops at the SQL level. It gives you PG::Connection and PG::Result and nothing above them.
Two documentation gaps are worth knowing about before you commit. The README does not document how to pin a specific libpq version for a source build beyond pointing at pg_config, and it does not document how to make the binary gem and the source gem produce identical results when a server type falls outside the coder coverage listed above. Check your own column types against the type table before you rely on exec_params to hand back Ruby objects instead of strings.
Editorial conclusion
pg fits Ruby services that talk to PostgreSQL 10 or later and want SQL without an ORM in between, provided you accept the Ruby 3.1 floor and the binary gem's single missing feature, LDAP connection options. It does not fit older PostgreSQL or Ruby deployments, and it is the wrong choice when you need LDAP based connection settings unless you force the source gem. Before adopting it, decide which install path you will standardize on, because the choice cannot be changed per connection at runtime.
Frequently asked questions
How do I install pg for Ruby?
Run gem install pg, which installs the binary gem for your platform by default, with libpq bundled in. To build from source against your own libpq, uninstall the binaries first with gem uninstall pg --all and then run gem install pg --platform ruby.
Does the pg binary gem support LDAP connection options?
No. The README states that retrieval of connection options from LDAP is the one use case the binary gems do not support, because it would require libldap and its dependencies. Forcing the source gem restores that support.
How do I get Ruby objects instead of strings from pg results?
Assign a type map to the connection, for example conn.type_map_for_results = PG::BasicTypeMapForResults.new conn, and set conn.type_map_for_queries for parameters. The project supports text and binary encoders and decoders for Integer, Float, Numeric and Boolean, and you can build custom PG::Coder objects with an OID and a format code.
Which PostgreSQL and Ruby versions does pg support?
The requirements are Ruby 3.1 or newer and PostgreSQL 10.x or later. The README states that pg works with PostgreSQL 10 and later.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ged-ruby-pg)Was this useful?