Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable full support for SQLite-JDBC using the JDBC adapter
SQLite-JDBC is now fully supported using the JDBC adapter. It passes the SQLite specs (which did have to be modified, but now support both the native and JDBC adapters. It passes the integration test suite. This was done similar to the PostgreSQL-JDBC adapter, by spliting the native adapter into shared and unshared parts, using the shared parts in the JDBC adapter, and reimplementing the unshared parts. This doesn't mean that the native and JDBC drivers operate exactly the same. For example, the native adapter will return strings for values such as computed columns, where the JDBC adapter will return numbers. The JDBC adapter also does not allow submitting multiple statements at once. Also, the JDBC adapter will raise a generic Error instead of Error::InvalidStatement if there is a problem with the query. One significant change is that Schema operations can now return arrays in addition to strings, and the Database object will handle it correctly. This was done to allow drop_column to work with SQLite-JDBC, since you can't submit multiple statements at once, and that is necessary to handle a column drop in SQLite. A type_test integration test has been added for testing that certain database types are supported. A few minor changes: * JDBC::Database::url alias to uri was added * Database::<< uses execute_ddl instead of execute * The SQLite specs no longer require the use of a memory database * You can use the SQLITE_URL constant instead of SQLITE_DB * You can use the SEQUEL_SQLITE_SPEC_DB environment variable as well * The dataset integration tests check delete and update return values
- Loading branch information
1 parent
332883a
commit f8aa8a3
Showing
11 changed files
with
400 additions
and
279 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
require 'sequel_core/adapters/shared/sqlite' | ||
|
||
module Sequel | ||
module JDBC | ||
module SQLite | ||
module DatabaseMethods | ||
include Sequel::SQLite::DatabaseMethods | ||
|
||
def dataset(opts=nil) | ||
Sequel::JDBC::SQLite::Dataset.new(self, opts) | ||
end | ||
|
||
def execute_insert(sql) | ||
begin | ||
log_info(sql) | ||
@pool.hold do |conn| | ||
stmt = conn.createStatement | ||
begin | ||
stmt.executeUpdate(sql) | ||
rs = stmt.executeQuery('SELECT last_insert_rowid()') | ||
rs.next | ||
rs.getInt(1) | ||
rescue NativeException, JavaSQL::SQLException => e | ||
raise Error, e.message | ||
ensure | ||
stmt.close | ||
end | ||
end | ||
rescue NativeException, JavaSQL::SQLException => e | ||
raise Error, "#{sql}\r\n#{e.message}" | ||
end | ||
end | ||
|
||
def transaction | ||
@pool.hold do |conn| | ||
@transactions ||= [] | ||
return yield(conn) if @transactions.include?(Thread.current) | ||
stmt = conn.createStatement | ||
begin | ||
log_info(Sequel::Database::SQL_BEGIN) | ||
stmt.execute(Sequel::Database::SQL_BEGIN) | ||
@transactions << Thread.current | ||
yield(conn) | ||
rescue Exception => e | ||
log_info(Sequel::Database::SQL_ROLLBACK) | ||
stmt.execute(Sequel::Database::SQL_ROLLBACK) | ||
raise e unless Error::Rollback === e | ||
ensure | ||
unless e | ||
log_info(Sequel::Database::SQL_COMMIT) | ||
stmt.execute(Sequel::Database::SQL_COMMIT) | ||
end | ||
stmt.close | ||
@transactions.delete(Thread.current) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def connection_pool_default_options | ||
o = super | ||
uri == 'jdbc:sqlite::memory:' ? o.merge(:max_connections=>1) : o | ||
end | ||
end | ||
|
||
class Dataset < JDBC::Dataset | ||
include Sequel::SQLite::DatasetMethods | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
module Sequel | ||
module SQLite | ||
module DatabaseMethods | ||
AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze | ||
SCHEMA_TYPE_RE = /\A(\w+)\((\d+)\)\z/ | ||
SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze | ||
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'" | ||
TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze | ||
|
||
def alter_table_sql(table, op) | ||
case op[:op] | ||
when :add_column | ||
super | ||
when :add_index | ||
index_definition_sql(table, op) | ||
when :drop_column | ||
columns_str = (schema_parse_table(table, {}).map{|c| c[0]} - Array(op[:name])).join(",") | ||
["BEGIN TRANSACTION", | ||
"CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})", | ||
"INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table}", | ||
"DROP TABLE #{table}", | ||
"CREATE TABLE #{table}(#{columns_str})", | ||
"INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup", | ||
"DROP TABLE #{table}_backup", | ||
"COMMIT"] | ||
else | ||
raise Error, "Unsupported ALTER TABLE operation" | ||
end | ||
end | ||
|
||
def auto_vacuum | ||
AUTO_VACUUM[pragma_get(:auto_vacuum).to_s] | ||
end | ||
|
||
def auto_vacuum=(value) | ||
value = AUTO_VACUUM.key(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.") | ||
pragma_set(:auto_vacuum, value) | ||
end | ||
|
||
def pragma_get(name) | ||
self["PRAGMA #{name}"].single_value | ||
end | ||
|
||
def pragma_set(name, value) | ||
execute_ddl("PRAGMA #{name} = #{value}") | ||
end | ||
|
||
def serial_primary_key_options | ||
{:primary_key => true, :type => :integer, :auto_increment => true} | ||
end | ||
|
||
def synchronous | ||
SYNCHRONOUS[pragma_get(:synchronous).to_s] | ||
end | ||
|
||
def synchronous=(value) | ||
value = SYNCHRONOUS.key(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.") | ||
pragma_set(:synchronous, value) | ||
end | ||
|
||
def tables | ||
self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym} | ||
end | ||
|
||
def temp_store | ||
TEMP_STORE[pragma_get(:temp_store).to_s] | ||
end | ||
|
||
def temp_store=(value) | ||
value = TEMP_STORE.key(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.") | ||
pragma_set(:temp_store, value) | ||
end | ||
|
||
private | ||
|
||
def schema_parse_table(table_name, opts) | ||
rows = self["PRAGMA table_info(?)", table_name].collect do |row| | ||
row.delete(:cid) | ||
row[:column] = row.delete(:name) | ||
row[:allow_null] = row.delete(:notnull).to_i == 0 ? 'YES' : 'NO' | ||
row[:default] = row.delete(:dflt_value) | ||
row[:primary_key] = row.delete(:pk).to_i == 1 ? true : false | ||
row[:db_type] = row.delete(:type) | ||
if m = SCHEMA_TYPE_RE.match(row[:db_type]) | ||
row[:db_type] = m[1] | ||
row[:max_chars] = m[2].to_i | ||
else | ||
row[:max_chars] = nil | ||
end | ||
row[:numeric_precision] = nil | ||
row | ||
end | ||
schema_parse_rows(rows) | ||
end | ||
|
||
def schema_parse_tables(opts) | ||
schemas = {} | ||
tables.each{|table| schemas[table] = schema_parse_table(table, opts)} | ||
schemas | ||
end | ||
end | ||
|
||
module DatasetMethods | ||
def complex_expression_sql(op, args) | ||
case op | ||
when :~, :'!~', :'~*', :'!~*' | ||
raise Error, "SQLite does not support pattern matching via regular expressions" | ||
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE' | ||
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets | ||
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})" | ||
else | ||
super(op, args) | ||
end | ||
end | ||
|
||
def delete(opts = nil) | ||
# check if no filter is specified | ||
unless (opts && opts[:where]) || @opts[:where] | ||
@db.transaction do | ||
unfiltered_count = count | ||
@db.execute_dui delete_sql(opts) | ||
unfiltered_count | ||
end | ||
else | ||
@db.execute_dui delete_sql(opts) | ||
end | ||
end | ||
|
||
def insert(*values) | ||
@db.execute_insert insert_sql(*values) | ||
end | ||
|
||
def insert_sql(*values) | ||
if (values.size == 1) && values.first.is_a?(Sequel::Dataset) | ||
"INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};" | ||
else | ||
super(*values) | ||
end | ||
end | ||
|
||
def quoted_identifier(c) | ||
"`#{c}`" | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.