Skip to content

Commit

Permalink
Support :auto_vacuum, :foreign_keys, :synchronous, and :temp_store Da…
Browse files Browse the repository at this point in the history
…tabase options on SQLite, for thread-safe PRAGMA setting

As mentioned to in the last commit message, previously Sequel's
support for setting PRAGMAs on SQLite was not thread safe. This
commit adds a thread safe method to set PRAGMAs by adding database
options for each supported PRAGMA.  Thread safe support for other
PRAGMAs can be done using the :after_connect option proc.

Because of the connection pool implementation, the only thread
safe way to make per connection modifications is before the
connection is added to the pool.  So in the native sqlite adapter
and the do and jdbc sqlite subadapters, the PRAGMA modification
SQL statements are executed on connection object object before
the connection is returned.

To reduce the amount of duplicated code, the private
connection_pragmas method is added to the shared SQL adapter.

Now that PRAGMAs can be modified safely, enable the foreign_keys
PRAGMA unless it is specifically disabled.  The only fallout in
the test suite from this is the need to change the order of
dropping of tables in the class table interitance plugin tests.
If you want to specifically disable the foreign_keys pragma,
set the :foreign_keys option to false when instantiating the
database.  This does break backwards compatibility, but allows
greater consistency with other databases.
  • Loading branch information
jeremyevans committed Apr 8, 2010
1 parent 34dbeb5 commit b4ca833
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 8 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG
@@ -1,6 +1,8 @@
=== HEAD

* Add foreign_keys accessor to SQLite Database objects, which modifies the foreign_keys PRAGMA available in 3.6.19+ (jeremyevans)
* Support :auto_vacuum, :foreign_keys, :synchronous, and :temp_store Database options on SQLite, for thread-safe PRAGMA setting (jeremyevans)

* Add foreign_keys accessor to SQLite Database objects (enabled by default), which modifies the foreign_keys PRAGMA available in 3.6.19+ (jeremyevans)

* Add an Database#sqlite_version method when connecting to SQLite, used to determine feature support (jeremyevans)

Expand Down
9 changes: 9 additions & 0 deletions lib/sequel/adapters/do/sqlite.rb
Expand Up @@ -20,6 +20,15 @@ def connection_pool_default_options
o = super
uri == 'sqlite3::memory:' ? o.merge(:max_connections=>1) : o
end

# Execute the connection pragmas on the connection
def setup_connection(conn)
connection_pragmas.each do |s|
com = conn.create_command(s)
log_yield(s){com.execute_non_query}
end
super
end
end

# Dataset class for SQLite datasets accessed via DataObjects.
Expand Down
12 changes: 12 additions & 0 deletions lib/sequel/adapters/jdbc/sqlite.rb
Expand Up @@ -32,6 +32,18 @@ def connection_pool_default_options
o = super
uri == 'jdbc:sqlite::memory:' ? o.merge(:max_connections=>1) : o
end

# Execute the connection pragmas on the connection.
def setup_connection(conn)
conn = super(conn)
begin
stmt = conn.createStatement
connection_pragmas.each{|s| log_yield(s){stmt.execute(s)}}
ensure
stmt.close if stmt
end
conn
end
end

# Dataset class for SQLite datasets accessed via JDBC.
Expand Down
39 changes: 33 additions & 6 deletions lib/sequel/adapters/shared/sqlite.rb
@@ -1,5 +1,8 @@
module Sequel
module SQLite
# No matter how you connect to SQLite, the following Database options
# can be used to set PRAGMAs on connections in a thread-safe manner:
# :auto_vacuum, :foreign_keys, :synchronous, and :temp_store.
module DatabaseMethods
AUTO_VACUUM = [:none, :full, :incremental].freeze
PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
Expand All @@ -21,7 +24,8 @@ def auto_vacuum
end

# Set the auto_vacuum PRAGMA using the given symbol (:none, :full, or
# :incremental).
# :incremental). See pragma_set. Consider using the :auto_vacuum
# Database option instead.
def auto_vacuum=(value)
value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
pragma_set(:auto_vacuum, value)
Expand All @@ -39,7 +43,8 @@ def foreign_keys
end

# Set the foreign_keys PRAGMA using the given boolean value, if using
# SQLite 3.6.19+. If not using 3.6.19+, no error is raised.
# SQLite 3.6.19+. If not using 3.6.19+, no error is raised. See pragma_set.
# Consider using the :foreign_keys Database option instead.
def foreign_keys=(value)
pragma_set(:foreign_keys, !!value ? 'on' : 'off') if sqlite_version >= 30619
end
Expand Down Expand Up @@ -73,15 +78,20 @@ def pragma_get(name)
end

# Set the value of the given PRAGMA to value.
#
# This method is not thread safe, and will not work correctly if there
# are multiple connections in the Database's connection pool. PRAGMA
# modifications should be done when the connection is created, using
# an option provided when creating the Database object.
def pragma_set(name, value)
execute_ddl("PRAGMA #{name} = #{value}")
end

# The version of the server as an integer, where 3.6.19 = 30619.
# If the server version can't be determined, 0 is used.
def sqlite_version
return @server_version if defined?(@server_version)
@server_version = begin
return @sqlite_version if defined?(@sqlite_version)
@sqlite_version = begin
v = get{sqlite_version{}}
[10000, 100, 1].zip(v.split('.')).inject(0){|a, m| a + m[0] * Integer(m[1])}
rescue
Expand All @@ -99,7 +109,8 @@ def synchronous
SYNCHRONOUS[pragma_get(:synchronous).to_i]
end

# Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full).
# Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full). See pragma_set.
# Consider using the :synchronous Database option instead.
def synchronous=(value)
value = SYNCHRONOUS.index(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
pragma_set(:synchronous, value)
Expand All @@ -119,7 +130,8 @@ def temp_store
TEMP_STORE[pragma_get(:temp_store).to_i]
end

# Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory).
# Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory). See pragma_set.
# Consider using the :temp_store Database option instead.
def temp_store=(value)
value = TEMP_STORE.index(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
pragma_set(:temp_store, value)
Expand Down Expand Up @@ -219,6 +231,21 @@ def identifier_input_method_default
def identifier_output_method_default
nil
end

# Array of PRAGMA SQL statements based on the Database options that should be applied to
# new connections.
def connection_pragmas
ps = []
v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
[[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
if v = opts[prag]
raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
ps << "PRAGMA #{prag} = #{v}"
end
end
ps
end

# Parse the output of the table_info pragma
def parse_pragma(table_name, opts)
Expand Down
2 changes: 2 additions & 0 deletions lib/sequel/adapters/sqlite.rb
Expand Up @@ -37,6 +37,8 @@ def connect(server)
db.busy_timeout(opts.fetch(:timeout, 5000))
db.type_translation = true

connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}

# Handle datetimes with Sequel.datetime_class
prok = proc do |t,v|
v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
Expand Down
2 changes: 1 addition & 1 deletion spec/integration/plugin_test.rb
Expand Up @@ -47,7 +47,7 @@ class ::Staff < Employee
clear_sqls
end
after do
@db.drop_table :executives, :managers, :staff, :employees
@db.drop_table :staff, :executives, :managers, :employees
[:Executive, :Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s)}
end

Expand Down

0 comments on commit b4ca833

Please sign in to comment.