• Stars
    star
    462
  • Rank 94,832 (Top 2 %)
  • Language
    Crystal
  • License
    BSD 3-Clause "New...
  • Created over 9 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

a postgres driver for crystal

crystal-pg

A native, non-blocking Postgres driver for Crystal

CI

usage

This driver now uses the crystal-db project. Documentation on connecting, querying, etc, can be found at:

shards

Add this to your shard.yml on a generated crystal project, and run shards install

dependencies:
  pg:
    github: will/crystal-pg

Example usage

require "db"
require "pg"

DB.open("postgres://user:pass@host:port/db_name?option1=a&option2=b") do |db|
   ... use db ...
end

More

crystal-pg also supports some functionality past the typical crystal-db usage:

Listen/Notify

There are two ways to listen for notifications. For docs on NOTIFY, please read https://www.postgresql.org/docs/current/static/sql-notify.html.

  1. Any connection can be given a callback to run on notifications. However they are only received when other traffic is going on.
  2. A special listen-only connection can be established for instant notification processing with PG.connect_listen.
# see full example in examples/listen_notify.cr
PG.connect_listen("postgres:///", "a", "b") do |n| # connect and  listen on "a" and "b"
  puts "    got: #{n.payload} on #{n.channel}"     # print notifications as they come in
end

Arrays

Crystal-pg supports several popular array types. If you only need a 1 dimensional array, you can cast down to the appropriate Crystal type:

PG_DB.query_one("select ARRAY[1, null, 3]", &.read(Array(Int32?))
# => [1, nil, 3]

PG_DB.query_one("select '{hello, world}'::text[]", &.read(Array(String))
# => ["hello", "world"]

Error Handling

It is possible to catch errors and notifications and pass them along to Crystal for further handling.

DB.connect("postgres:///") do |cnn|
  # Capture and print all exceptions
  cnn.on_notice { |x| puts "pgSQL #{x}" }

  # A function that raises exceptions
  cnn.exec(
    <<-SQL
      CREATE OR REPLACE FUNCTION foo(IN str TEXT)
        RETURNS VOID
        LANGUAGE 'plpgsql'
        AS $$
          BEGIN
            IF str = 'yes' THEN
                    RAISE NOTICE 'Glad we agree!';
            ELSE
              RAISE EXCEPTION 'You know nothing John Snow!';
            END IF;
          END;
        $$;
    SQL
  )

  # Notice handling example
  cnn.exec(
    <<-SQL
      SELECT foo('yes');
    SQL
  )
  # => pgSQL NOTICE: Glad we agree!

  # Exception handling example
  cnn.exec(
    <<-SQL
      SELECT foo('no');
    SQL
  )
  # => pgSQL ERROR: You know nothing John Snow!
  #    Unhandled exception: You know nothing John Snow! (PQ::PQError)
  #     from lib/pg/src/pq/connection.cr:203:7 in 'handle_error'
  #     from lib/pg/src/pq/connection.cr:186:7 in 'handle_async_frames'
  #     from lib/pg/src/pq/connection.cr:162:7 in 'read'
  #     from lib/pg/src/pq/connection.cr:386:18 in 'expect_frame'
  #     from lib/pg/src/pq/connection.cr:370:9 in 'read_next_row_start'
  #     from lib/pg/src/pg/result_set.cr:39:8 in 'move_next'
  #     from lib/pg/src/pg/statement.cr:39:13 in 'perform_exec'
  #     from lib/db/src/db/statement.cr:82:14 in 'perform_exec_and_release'
  #     from lib/db/src/db/statement.cr:68:7 in 'exec:args'
  #     from lib/db/src/db/query_methods.cr:271:7 in 'exec'
  #     from spec/cerebrum_spec.cr:84:3 in '__crystal_main'
  #     from /usr/share/crystal/src/crystal/main.cr:97:5 in 'main_user_code'
  #     from /usr/share/crystal/src/crystal/main.cr:86:7 in 'main'
  #     from /usr/share/crystal/src/crystal/main.cr:106:3 in 'main'
  #     from __libc_start_main
  #     from _start
  #     from ???

Requirements

Crystal-pg is regularly tested on the Postgres versions the Postgres project itself supports. Since it uses protocol version 3, older versions probably also work but are not guaranteed.

Supported Datatypes

  • text
  • boolean
  • int8, int4, int2
  • float4, float8
  • timestamptz, date, timestamp (but no one should use ts when tstz exists!)
  • json and jsonb
  • uuid
  • bytea
  • numeric/decimal (1)
  • varchar
  • regtype
  • geo types: point, box, path, lseg, polygon, circle, line
  • array types: int8, int4, int2, float8, float4, bool, text, numeric, timestamptz, date, timestamp
  • interval (2)

1: A note on numeric: In Postgres this type has arbitrary precision. In this driver, it is represented as a PG::Numeric which retains all precision, but if you need to do any math on it, you will probably need to cast it to a float first. If you need true arbitrary precision, you can optionally require pg_ext/big_rational which adds #to_big_r, but requires that you have LibGMP installed.

2: A note on interval: A Postgres interval can not be directly mapped to a built in Crystal datatype. Therfore we provide a PG::Interval type that can be converted to Time::Span and Time::MonthSpan.

Authentication Methods

By default this driver will accept scram-sha-256 and md5, as well as trust. However cleartext is disabled by default. You can control exactly which auth methods the client will accept by passing in a comma separated list to the auth_methods parameter, for example

 DB.open("postgres://example.com/dbname?auth_methods=cleartext,md5,scram-sha-256")

DO NOT TURN cleartext ON UNLESS YOU ABSOLUTELY NEED IT! Merely by having this option enabled exposes a postgres client to downgrade man-in-the-middle attacks, even if the server is configured to not support cleartext. Even if you use TLS, you are not safe unless you are fully verifying the server's cert, as the attacker can terminate TLS and re-negotiate a connection with the server.

client                     attacker                     server
----------------------------------------------------------------------------
I want to connect \
                   \->  intercepts, forwards
                        I want to connect \
                                           \----->  receives connection request

                                                  / I support scram and/or md5 only
                        intercepts, sends      <-/
                     /  I only support cleartext
receives attacker <-/
claiming server
only supports cleartext
sends password because
cleartext enabled \
                   \->  receives clear password,
                        negotiates scram/md5
                        with real server      \
                                               \--> accepts scram/md5 auth

It is a mistake for any driver to support cleartext by default, and it's a mistake that postgres continues to have this as an option at all.

More Repositories

1

slacktyping

i'm typing when you're typing
Ruby
1,918
star
2

redirect_blame

It's your fault my site is down.
HTML
502
star
3

datascope

postgres 9.2 visibility
JavaScript
259
star
4

githubprofilecheat

try two
Ruby
102
star
5

bundle

gem install bundle
66
star
6

git-vain

vanity git
Zig
64
star
7

tree-sitter-crystal

C
21
star
8

slouch

JavaScript
17
star
9

psqlstarwars

HTML
16
star
10

yeah_i_did

Helpfully fixes your typos
Ruby
13
star
11

bgwinch.nvim

set neovim background on SIGWINCH
Lua
12
star
12

Rails3_CouchRest_Model_example

Just a small app to show how Rails3 and CouchRest::Model go together
Ruby
10
star
13

ghpc

Ruby
7
star
14

githubprofilecheat2

same as one but with more
7
star
15

crystal_workbook

crystal playground for my talk
HTML
6
star
16

english_key

RFC1751 for ruby
Ruby
5
star
17

pgcmd

pgcmd
Ruby
4
star
18

fp

Crystal
4
star
19

heroku_completion_generator

tabing to victory since oh-eleven
Ruby
4
star
20

will.github.com

will.github.com
HTML
4
star
21

oblique

a random oblique strategy
Zig
3
star
22

switchbox

declarative ruby conditions
Ruby
3
star
23

phcresolve

postgres.heroku.com resolver
Ruby
3
star
24

haskell-tmbundle

Haskell TextMate bundle
3
star
25

torch

shining light
Ruby
3
star
26

croner

Ruby
2
star
27

incdeploy_webx

Ruby
2
star
28

heroku-buildpack-redline

buildpack for redline smalltalk
Shell
2
star
29

fauxbinius

Ruby in ruby
2
star
30

songalive-signup

JavaScript
2
star
31

ppm2emoji

Crystal
2
star
32

autobg.nvim

auto sync vim bg with mac appearnace
Lua
2
star
33

pg-track-fork-demo

heroku postgres track and fork demo
JavaScript
2
star
34

heroku-resolve-follower

Ruby
2
star
35

embedclip

embed heroku postgres dataclip
Ruby
2
star
36

umibe

initial attempt at getting smalltalk on heroku
2
star
37

bitfissionhitcounter

the hit counter for bitfission
Crystal
1
star
38

make_me

having fun with gh pages
1
star
39

pathwayfindr

Ruby
1
star
40

realistic-aws-status

realtalk
1
star
41

arduino_ci_indicator

Arduino Continuos Integration Indicator
Ruby
1
star
42

mark_sinatra

sinatra app for markdown sites on heroku
Ruby
1
star
43

quicksquare

adds the transaction fees to square
JavaScript
1
star
44

eliteissue

5nyp3 155u35 0n g17hub
Ruby
1
star
45

mayfly

ephemeral process manager and maybe queue
Ruby
1
star
46

crow

Easy API mock endpoints
Ruby
1
star
47

bikeshed

Ruby
1
star
48

cpge

a Crystal PostGres Exmaple
Crystal
1
star
49

eatclub

Crystal
1
star
50

autoaaron

Ruby
1
star
51

slow

a sinusoidally slow app
Ruby
1
star
52

itwss

JavaScript
1
star
53

tryderby

me trying derby
CoffeeScript
1
star
54

waitpr

wait on github pr checks
Crystal
1
star
55

sudoku-solver

simple sudoku solver
Ruby
1
star