Permalink
Browse files

Make Database#transaction on PostgreSQL recognize :read_only and :def…

…errable options

In addition to setting the transaction isolation level, PostgreSQL
also supports these additional options.  READ ONLY ensures that
most data changing commands are not allowed.  DEFERRABLE is only
important for transactions that are both SERIALIZABLE and READ
ONLY, in which case it has less overhead than a standard
SERIALIZABLE transaction.
  • Loading branch information...
1 parent 229ea97 commit 3710b1d5997eb7454c49599cf5bd2a6806e22713 @jeremyevans committed May 30, 2012
Showing with 45 additions and 4 deletions.
  1. +3 −1 CHANGELOG
  2. +15 −1 lib/sequel/adapters/shared/postgres.rb
  3. +7 −2 lib/sequel/database/query.rb
  4. +20 −0 spec/adapters/postgres_spec.rb
View
@@ -1,5 +1,7 @@
=== HEAD
+* Add document explaining Sequel's object model (jeremyevans)
+
* Attempt to detect more disconnect errors in the mysql2 adapter (jeremyevans)
* Add is_current? and check_current to the migrators, for checking/raising if there are unapplied migrations (pvh, jeremyevans) (#487)
@@ -38,7 +40,7 @@
* Support :concurrently option when adding and dropping indexes on PostgreSQL (jeremyevans)
-* Make Database#transaction on PostgreSQL accept a :synchronous option to change synchronous_commit setting for that transaction (jeremyevans)
+* Make Database#transaction on PostgreSQL recognize :synchronous, :read_only, and :deferrable options (jeremyevans)
* Support :sql_mode option when connecting to MySQL (jeremyevans)
@@ -462,7 +462,7 @@ def begin_new_transaction(conn, opts)
log_connection_execute(conn, "SET LOCAL synchronous_commit = #{sync}")
end
end
-
+
# If the :prepare option is given and we aren't in a savepoint,
# prepare the transaction for a two-phase commit.
def commit_transaction(conn, opts={})
@@ -666,6 +666,20 @@ def schema_parse_table(table_name, opts)
end
end
+ # Set the transaction isolation level on the given connection
+ def set_transaction_isolation(conn, opts)
+ level = opts.fetch(:isolation, transaction_isolation_level)
+ set_read_mode = opts.has_key?(:read_only)
+ set_deferrable_mode = opts.has_key?(:deferrable)
+ if level || set_read_mode || set_deferrable_mode
+ sql = "SET TRANSACTION"
+ sql << " ISOLATION LEVEL #{Sequel::Database::TRANSACTION_ISOLATION_LEVELS[level]}" if level
+ sql << " READ #{opts[:read_only] ? 'ONLY' : 'WRITE'}" if set_read_mode
+ sql << " #{'NOT ' unless opts[:deferrable]}DEFERRABLE" if set_deferrable_mode
+ log_connection_execute(conn, sql)
+ end
+ end
+
# Turns an array of argument specifiers into an SQL fragment used for function arguments. See create_function_sql.
def sql_function_args(args)
"(#{Array(args).map{|a| Array(a).reverse.join(' ')}.join(', ')})"
@@ -232,7 +232,7 @@ def tables(opts={})
# either all statements are successful or none of the statements are
# successful. Note that MySQL MyISAM tabels do not support transactions.
#
- # The following options are respected:
+ # The following general options are respected:
#
# :isolation :: The transaction isolation level to use for this transaction,
# should be :uncommitted, :committed, :repeatable, or :serializable,
@@ -249,7 +249,12 @@ def tables(opts={})
# only respected if the database/adapter supports savepoints. By
# default Sequel will reuse an existing transaction, so if you want to
# use a savepoint you must use this option.
- # :synchronous :: (PostgreSQL only) if non-nil, set synchronous_commit
+ #
+ # PostgreSQL specific options:
+ #
+ # :deferrable :: (9.1+) If present, set to DEFERRABLE if true or NOT DEFERRABLE if false.
+ # :read_only :: If present, set to READ ONLY if true or READ WRITE if false.
+ # :synchronous :: if non-nil, set synchronous_commit
# appropriately. Valid values true, :on, false, :off, :local (9.1+),
# and :remote_write (9.2+).
def transaction(opts={}, &block)
@@ -183,6 +183,26 @@ def logger.method_missing(m, msg)
end
end
+ specify "should have #transaction support read only transactions" do
+ @db = POSTGRES_DB
+ @db.transaction(:read_only=>true){}
+ @db.transaction(:read_only=>false){}
+ @db.transaction(:isolation=>:serializable, :read_only=>true){}
+ @db.transaction(:isolation=>:serializable, :read_only=>false){}
+ @db.sqls.grep(/READ/).should == ["SET TRANSACTION READ ONLY", "SET TRANSACTION READ WRITE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"]
+ end
+
+ specify "should have #transaction support deferrable transactions" do
+ @db = POSTGRES_DB
+ @db.transaction(:deferrable=>true){}
+ @db.transaction(:deferrable=>false){}
+ @db.transaction(:deferrable=>true, :read_only=>true){}
+ @db.transaction(:deferrable=>false, :read_only=>false){}
+ @db.transaction(:isolation=>:serializable, :deferrable=>true, :read_only=>true){}
+ @db.transaction(:isolation=>:serializable, :deferrable=>false, :read_only=>false){}
+ @db.sqls.grep(/DEF/).should == ["SET TRANSACTION DEFERRABLE", "SET TRANSACTION NOT DEFERRABLE", "SET TRANSACTION READ ONLY DEFERRABLE", "SET TRANSACTION READ WRITE NOT DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE", "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE NOT DEFERRABLE"]
+ end if POSTGRES_DB.server_version >= 90100
+
specify "should support creating indexes concurrently" do
POSTGRES_DB.sqls.clear
POSTGRES_DB.add_index :test, [:name, :value], :concurrently=>true

0 comments on commit 3710b1d

Please sign in to comment.