Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/780e839513ebdcf3cd0419a414d1f602 to your computer and use it in GitHub Desktop.
Save JoshCheek/780e839513ebdcf3cd0419a414d1f602 to your computer and use it in GitHub Desktop.
Using PostgreSQL's savepoints to recover from errors while inside a transaction
# these are Ruby 3's endless methods
require 'pg'
db = PG.connect
def db.exec_params(...) = super.map { |h| h.transform_keys &:intern }
def db.exec(...) = super.map { |h| h.transform_keys &:intern }
# transactions methods
def db.begin() = exec('begin')
def db.save(name) = exec("savepoint #{name}")
def db.rollback_to(name) = exec("rollback to #{name}")
def db.select() = exec("select * from omgs")
def db.insert(*vals) = exec_params(<<~SQL, vals)
insert into omgs (val)
values #{vals.map.with_index(1) { |_, i| "($#{i})" }.join(", ")}
returning *
SQL
# A transaction with a table and 3 rows
db.begin
db.exec 'create table omgs(val integer)'
db.insert 11, 22
db.save 'has_1122'
# We can rollback the inserted data while still leaving the transaction open
db.insert 33, 44
db.select # => [{:val=>"11"}, {:val=>"22"}, {:val=>"33"}, {:val=>"44"}]
db.rollback_to 'has_1122'
db.select # => [{:val=>"11"}, {:val=>"22"}]
# Errors will prevent us from running new commands
# becuase "current transaction is aborted"
db.insert 55, 66
db.select # => [{:val=>"11"}, {:val=>"22"}, {:val=>"55"}, {:val=>"66"}]
db.exec 'omg!' rescue $! # => #<PG::SyntaxError: ERROR: syntax error at or near "omg"\nLINE 1: omg!\n ^\n>
db.select rescue $! # => #<PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block\n>
# But we can rollback to that last savepoint and then try again
db.rollback_to 'has_1122'
db.select # => [{:val=>"11"}, {:val=>"22"}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment