diff --git a/CHANGELOG b/CHANGELOG index 9681ea7d9e..363d2dffe7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ === HEAD +* Enable full support for SQLite-JDBC using the JDBC adapter (jeremyevans) + * Minor changes to allow for full Ruby 1.9 compatibility (jeremyevans) * Make Database#disconnect work for the ADO adapter (spicyj) diff --git a/lib/sequel_core/adapters/jdbc.rb b/lib/sequel_core/adapters/jdbc.rb index 108a6ddae9..d2b2dadc7c 100644 --- a/lib/sequel_core/adapters/jdbc.rb +++ b/lib/sequel_core/adapters/jdbc.rb @@ -7,22 +7,29 @@ module JavaSQL; include_package 'java.sql'; end DATABASE_SETUP = {:postgresql=>proc do |db| require 'sequel_core/adapters/jdbc/postgresql' db.extend(Sequel::JDBC::Postgres::DatabaseMethods) - begin - require 'jdbc/postgres' - rescue LoadError - # jdbc-postgres gem not used, hopefully the user has the - # PostgreSQL-JDBC .jar in their CLASSPATH - end + JDBC.load_gem('postgres') org.postgresql.Driver end, - :mysql=>proc{com.mysql.jdbc.Driver}, - :sqlite=>proc{org.sqlite.JDBC}, + :mysql=>proc do |db| + JDBC.load_gem('mysql') + com.mysql.jdbc.Driver + end, + :sqlite=>proc do |db| + require 'sequel_core/adapters/jdbc/sqlite' + db.extend(Sequel::JDBC::SQLite::DatabaseMethods) + JDBC.load_gem('sqlite3') + org.sqlite.JDBC + end, :oracle=>proc{oracle.jdbc.driver.OracleDriver}, :sqlserver=>proc{com.microsoft.sqlserver.jdbc.SQLServerDriver} } - def self.load_driver(driver) - JavaLang::Class.forName(driver) + def self.load_gem(name) + begin + require "jdbc/#{name}" + rescue LoadError + # jdbc gem not used, hopefully the user has the .jar in their CLASSPATH + end end class Database < Sequel::Database @@ -57,7 +64,7 @@ def execute(sql) stmt = conn.createStatement begin yield stmt.executeQuery(sql) - rescue NativeException => e + rescue NativeException, JavaSQL::SQLException => e raise Error, e.message ensure stmt.close @@ -71,7 +78,7 @@ def execute_ddl(sql) stmt = conn.createStatement begin stmt.execute(sql) - rescue NativeException => e + rescue NativeException, JavaSQL::SQLException => e raise Error, e.message ensure stmt.close @@ -85,7 +92,7 @@ def execute_dui(sql) stmt = conn.createStatement begin stmt.executeUpdate(sql) - rescue NativeException => e + rescue NativeException, JavaSQL::SQLException => e raise Error, e.message ensure stmt.close @@ -101,6 +108,7 @@ def uri ur = @opts[:uri] || @opts[:url] || @opts[:database] ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}" end + alias url uri private diff --git a/lib/sequel_core/adapters/jdbc/sqlite.rb b/lib/sequel_core/adapters/jdbc/sqlite.rb new file mode 100644 index 0000000000..1969675695 --- /dev/null +++ b/lib/sequel_core/adapters/jdbc/sqlite.rb @@ -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 diff --git a/lib/sequel_core/adapters/shared/sqlite.rb b/lib/sequel_core/adapters/shared/sqlite.rb new file mode 100644 index 0000000000..673add5827 --- /dev/null +++ b/lib/sequel_core/adapters/shared/sqlite.rb @@ -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 diff --git a/lib/sequel_core/adapters/sqlite.rb b/lib/sequel_core/adapters/sqlite.rb index 475fc51643..786f333d82 100644 --- a/lib/sequel_core/adapters/sqlite.rb +++ b/lib/sequel_core/adapters/sqlite.rb @@ -1,8 +1,11 @@ require 'sqlite3' +require 'sequel_core/adapters/shared/sqlite' module Sequel module SQLite class Database < Sequel::Database + include ::Sequel::SQLite::DatabaseMethods + set_adapter_scheme :sqlite def self.uri_to_options(uri) # :nodoc: @@ -10,10 +13,6 @@ def self.uri_to_options(uri) # :nodoc: end private_class_method :uri_to_options - - def serial_primary_key_options - {:primary_key => true, :type => :integer, :auto_increment => true} - end def connect @opts[:database] = ':memory:' if @opts[:database].blank? @@ -27,18 +26,12 @@ def connect db end - def disconnect - @pool.disconnect {|c| c.close} - end - def dataset(opts = nil) SQLite::Dataset.new(self, opts) end - TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'" - - def tables - self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym} + def disconnect + @pool.disconnect {|c| c.close} end def execute(sql) @@ -77,68 +70,6 @@ def execute_select(sql, &block) end end - def pragma_get(name) - single_value("PRAGMA #{name}") - end - - def pragma_set(name, value) - execute("PRAGMA #{name} = #{value}") - end - - AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze - - def auto_vacuum - AUTO_VACUUM[pragma_get(:auto_vacuum)] - 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 - - SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze - - def synchronous - SYNCHRONOUS[pragma_get(:synchronous)] - 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 - - TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze - - def temp_store - TEMP_STORE[pragma_get(:temp_store)] - 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 - - 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] ? op[:name] : [op[:name]])).join(",") - sql = "BEGIN TRANSACTION;\n" - sql += "CREATE TEMPORARY TABLE #{table}_backup(#{columns_str});\n" - sql += "INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table};\n" - sql += "DROP TABLE #{table};\n" - sql += "CREATE TABLE #{table}(#{columns_str});\n" - sql += "INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup;\n" - sql += "DROP TABLE #{table}_backup;\n" - sql += "COMMIT;\n" - else - raise Error, "Unsupported ALTER TABLE operation" - end - end - def transaction(&block) @pool.hold do |conn| if conn.transaction_active? @@ -163,38 +94,29 @@ def connection_pool_default_options o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank? o end - - SCHEMA_TYPE_RE = /\A(\w+)\((\d+)\)\z/ - def schema_parse_table(table_name, opts) - rows = self["PRAGMA table_info('#{::SQLite3::Database.quote(table_name.to_s)}')"].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 class Dataset < Sequel::Dataset - def quoted_identifier(c) - "`#{c}`" + include ::Sequel::SQLite::DatasetMethods + + EXPLAIN = 'EXPLAIN %s'.freeze + + def explain + res = [] + @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r} + res + end + + def fetch_rows(sql) + @db.execute_select(sql) do |result| + @columns = result.columns.map {|c| c.to_sym} + column_count = @columns.size + result.each do |values| + row = {} + column_count.times {|i| row[@columns[i]] = values[i]} + yield row + end + end end def literal(v) @@ -211,63 +133,6 @@ def literal(v) super end end - - 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 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 fetch_rows(sql, &block) - @db.execute_select(sql) do |result| - @columns = result.columns.map {|c| c.to_sym} - column_count = @columns.size - result.each do |values| - row = {} - column_count.times {|i| row[@columns[i]] = values[i]} - block.call(row) - end - end - end - - def insert(*values) - @db.execute_insert insert_sql(*values) - 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 delete_sql(opts) - unfiltered_count - end - else - @db.execute delete_sql(opts) - end - end - - EXPLAIN = 'EXPLAIN %s'.freeze - - def explain - res = [] - @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r} - res - end end end end diff --git a/lib/sequel_core/database.rb b/lib/sequel_core/database.rb index 119643165b..5c37142893 100644 --- a/lib/sequel_core/database.rb +++ b/lib/sequel_core/database.rb @@ -174,7 +174,7 @@ def self.uri_to_options(uri) # :nodoc: # or as an array of strings. If an array is given, comments and excessive # white space are removed. See also Array#to_sql. def <<(sql) - execute((Array === sql) ? sql.to_sql : sql) + execute_ddl((Array === sql) ? sql.to_sql : sql) end # Returns a dataset from the database. If the first argument is a string, diff --git a/lib/sequel_core/database/schema.rb b/lib/sequel_core/database/schema.rb index 29372e4ed0..e63d28434c 100644 --- a/lib/sequel_core/database/schema.rb +++ b/lib/sequel_core/database/schema.rb @@ -41,7 +41,7 @@ def add_index(table, *args) # See Schema::AlterTableGenerator. def alter_table(name, generator=nil, &block) generator ||= Schema::AlterTableGenerator.new(self, &block) - alter_table_sql_list(name, generator.operations).each {|sql| execute_ddl(sql)} + alter_table_sql_list(name, generator.operations).flatten.each {|sql| execute_ddl(sql)} end # Creates a table with the columns given in the provided block: @@ -56,7 +56,7 @@ def alter_table(name, generator=nil, &block) # See Schema::Generator. def create_table(name, generator=nil, &block) generator ||= Schema::Generator.new(self, &block) - create_table_sql_list(name, *generator.create_info).each {|sql| execute_ddl(sql)} + create_table_sql_list(name, *generator.create_info).flatten.each {|sql| execute_ddl(sql)} end # Forcibly creates a table. If the table already exists it is dropped. diff --git a/spec/adapters/sqlite_spec.rb b/spec/adapters/sqlite_spec.rb index f89ba19cf9..2f185e5753 100644 --- a/spec/adapters/sqlite_spec.rb +++ b/spec/adapters/sqlite_spec.rb @@ -1,56 +1,46 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb') unless defined?(SQLITE_DB) - SQLITE_DB = Sequel.connect('sqlite:/') + SQLITE_URL = 'sqlite:/' unless defined? SQLITE_URL + SQLITE_DB = Sequel.connect(ENV['SEQUEL_SQLITE_SPEC_DB']||SQLITE_URL) end -SQLITE_DB.create_table :items do - integer :id, :primary_key => true, :auto_increment => true - text :name - float :value -end -SQLITE_DB.create_table :test2 do - text :name - integer :value -end -SQLITE_DB.create_table(:time) {timestamp :t} - context "An SQLite database" do before do - @db = Sequel.connect('sqlite:/') + @db = SQLITE_DB end - after do - @db.disconnect + + if SQLITE_DB.auto_vacuum == :none + specify "should support getting pragma values" do + @db.pragma_get(:auto_vacuum).to_s.should == '0' + end + + specify "should support setting pragma values" do + @db.pragma_set(:auto_vacuum, '1') + @db.pragma_get(:auto_vacuum).to_s.should == '1' + @db.pragma_set(:auto_vacuum, '2') + @db.pragma_get(:auto_vacuum).to_s.should == '2' + end + + specify "should support getting and setting the auto_vacuum pragma" do + @db.auto_vacuum = :full + @db.auto_vacuum.should == :full + @db.auto_vacuum = :incremental + @db.auto_vacuum.should == :incremental + + proc {@db.auto_vacuum = :invalid}.should raise_error(Sequel::Error) + end end specify "should provide a list of existing tables" do - @db.tables.should == [] - - @db.create_table :testing do + @db.drop_table(:testing) rescue nil + @db.tables.should be_a_kind_of(Array) + @db.tables.should_not include(:testing) + @db.create_table! :testing do text :name end @db.tables.should include(:testing) end - - specify "should support getting pragma values" do - @db.pragma_get(:auto_vacuum).should == '0' - end - - specify "should support setting pragma values" do - @db.pragma_set(:auto_vacuum, '1') - @db.pragma_get(:auto_vacuum).should == '1' - @db.pragma_set(:auto_vacuum, '2') - @db.pragma_get(:auto_vacuum).should == '2' - end - - specify "should support getting and setting the auto_vacuum pragma" do - @db.auto_vacuum = :full - @db.auto_vacuum.should == :full - @db.auto_vacuum = :incremental - @db.auto_vacuum.should == :incremental - - proc {@db.auto_vacuum = :invalid}.should raise_error(Sequel::Error) - end specify "should support getting and setting the synchronous pragma" do @db.synchronous = :off @@ -74,94 +64,76 @@ proc {@db.temp_store = :invalid}.should raise_error(Sequel::Error) end - specify "should be able to execute multiple statements at once" do - @db.create_table :t do - text :name - end - - @db << "insert into t (name) values ('abc');insert into t (name) values ('def')" - - @db[:t].count.should == 2 - - @db[:t].order(:name).map(:name).should == ['abc', 'def'] - end - specify "should be able to execute transactions" do @db.transaction do - @db.create_table(:t) {text :name} + @db.create_table!(:t) {text :name} end - @db.tables.should == [:t] + @db.tables.should include(:t) proc {@db.transaction do - @db.create_table(:u) {text :name} + @db.create_table!(:u) {text :name} raise ArgumentError end}.should raise_error(ArgumentError) # no commit - @db.tables.should == [:t] + @db.tables.should_not include(:u) proc {@db.transaction do - @db.create_table(:v) {text :name} + @db.create_table!(:v) {text :name} raise Sequel::Error::Rollback end}.should_not raise_error # no commit - @db.tables.should == [:t] + @db.tables.should_not include(:r) end specify "should support nested transactions" do @db.transaction do @db.transaction do - @db.create_table(:t) {text :name} + @db.create_table!(:t) {text :name} end end - @db.tables.should == [:t] + @db.tables.should include(:t) proc {@db.transaction do - @db.create_table(:v) {text :name} + @db.create_table!(:v) {text :name} @db.transaction do raise Sequel::Error::Rollback # should roll back the top-level transaction end end}.should_not raise_error # no commit - @db.tables.should == [:t] + @db.tables.should_not include(:v) end specify "should handle returning inside of transaction by committing" do - @db.create_table(:items){text :name} + @db.create_table!(:items2){text :name} def @db.ret_commit transaction do - self[:items] << {:name => 'abc'} + self[:items2] << {:name => 'abc'} return - self[:items] << {:name => 'd'} + self[:items2] << {:name => 'd'} end end - @db[:items].count.should == 0 + @db[:items2].count.should == 0 @db.ret_commit - @db[:items].count.should == 1 + @db[:items2].count.should == 1 @db.ret_commit - @db[:items].count.should == 2 + @db[:items2].count.should == 2 proc do @db.transaction do raise Interrupt, 'asdf' end end.should raise_error(Interrupt) - @db[:items].count.should == 2 - end - - specify "should provide disconnect functionality" do - @db.tables - @db.pool.size.should == 1 - @db.disconnect - @db.pool.size.should == 0 + @db[:items2].count.should == 2 end specify "should support timestamps" do t1 = Time.at(Time.now.to_i) #normalize time - - SQLITE_DB[:time] << {:t => t1} - SQLITE_DB[:time].first[:t].should == t1 + @db.create_table!(:time) {timestamp :t} + @db[:time] << {:t => t1} + x = @db[:time].first[:t] + [t1.iso8601, t1.to_s].should include(x.respond_to?(:iso8601) ? x.iso8601 : x.to_s) end specify "should support sequential primary keys" do @@ -176,18 +148,9 @@ def @db.ret_commit ] end - specify "should catch invalid SQL errors and raise them as Error::InvalidStatement" do - proc {@db.execute 'blah blah'}.should raise_error( - Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error") - - proc {@db.execute_insert 'blah blah'}.should raise_error( - Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error") - - proc {@db.execute_select 'blah blah'}.should raise_error( - Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error") - - proc {@db.single_value 'blah blah'}.should raise_error( - Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error") + specify "should catch invalid SQL errors and raise them as Error" do + proc {@db.execute 'blah blah'}.should raise_error(Sequel::Error) + proc {@db.execute_insert 'blah blah'}.should raise_error(Sequel::Error) end specify "should not swallow non-SQLite based exceptions" do @@ -195,18 +158,23 @@ def @db.ret_commit end specify "should correctly parse the schema" do - @db.create_table(:time) {timestamp :t} - @db.schema(:time, :reload=>true).should == [[:t, {:type=>:datetime, :allow_null=>true, :max_chars=>nil, :default=>nil, :db_type=>"timestamp", :numeric_precision=>nil, :primary_key=>false}]] + @db.create_table!(:time2) {timestamp :t} + @db.schema(:time2, :reload=>true).should == [[:t, {:type=>:datetime, :allow_null=>true, :max_chars=>nil, :default=>nil, :db_type=>"timestamp", :numeric_precision=>nil, :primary_key=>false}]] end specify "should get the schema all database tables if no table name is used" do - @db.create_table(:time) {timestamp :t} - @db.schema(:time, :reload=>true).should == @db.schema(nil, :reload=>true)[:time] + @db.create_table!(:time2) {timestamp :t} + @db.schema(:time2, :reload=>true).should == @db.schema(nil, :reload=>true)[:time] end end context "An SQLite dataset" do setup do + SQLITE_DB.create_table! :items do + integer :id, :primary_key => true, :auto_increment => true + text :name + float :value + end @d = SQLITE_DB[:items] @d.delete # remove all records end @@ -274,6 +242,11 @@ def @db.ret_commit context "An SQLite dataset" do setup do + SQLITE_DB.create_table! :items do + integer :id, :primary_key => true, :auto_increment => true + text :name + float :value + end @d = SQLITE_DB[:items] @d.delete # remove all records @d << {:name => 'abc', :value => 1.23} @@ -282,24 +255,29 @@ def @db.ret_commit end specify "should correctly return avg" do - @d.avg(:value).should == ((1.23 + 4.56 + 7.89) / 3).to_s + @d.avg(:value).to_s.should == ((1.23 + 4.56 + 7.89) / 3).to_s end specify "should correctly return sum" do - @d.sum(:value).should == (1.23 + 4.56 + 7.89).to_s + @d.sum(:value).to_s.should == (1.23 + 4.56 + 7.89).to_s end specify "should correctly return max" do - @d.max(:value).should == 7.89.to_s + @d.max(:value).to_s.should == 7.89.to_s end specify "should correctly return min" do - @d.min(:value).should == 1.23.to_s + @d.min(:value).to_s.should == 1.23.to_s end end context "SQLite::Dataset#delete" do setup do + SQLITE_DB.create_table! :items do + integer :id, :primary_key => true, :auto_increment => true + text :name + float :value + end @d = SQLITE_DB[:items] @d.delete # remove all records @d << {:name => 'abc', :value => 1.23} @@ -327,6 +305,11 @@ def @db.ret_commit context "SQLite::Dataset#update" do setup do + SQLITE_DB.create_table! :items do + integer :id, :primary_key => true, :auto_increment => true + text :name + float :value + end @d = SQLITE_DB[:items] @d.delete # remove all records @d << {:name => 'abc', :value => 1.23} @@ -345,12 +328,16 @@ def @db.ret_commit context "SQLite dataset" do setup do - SQLITE_DB.create_table :test do + SQLITE_DB.create_table! :test do + integer :id, :primary_key => true, :auto_increment => true + text :name + float :value + end + SQLITE_DB.create_table! :items do integer :id, :primary_key => true, :auto_increment => true text :name float :value end - @d = SQLITE_DB[:items] @d.delete # remove all records @d << {:name => 'abc', :value => 1.23} diff --git a/spec/integration/dataset_test.rb b/spec/integration/dataset_test.rb index 9998b521a9..b3c88b754c 100644 --- a/spec/integration/dataset_test.rb +++ b/spec/integration/dataset_test.rb @@ -28,13 +28,13 @@ end specify "should delete correctly" do - @ds.filter(1=>1).delete + @ds.filter(1=>1).delete.should == 1 sqls_should_be('DELETE FROM items WHERE (1 = 1)') @ds.count.should == 0 end specify "should update correctly" do - @ds.update(:number=>:number+1) + @ds.update(:number=>:number+1).should == 1 sqls_should_be('UPDATE items SET number = (number + 1)') @ds.all.should == [{:id=>1, :number=>11}] end diff --git a/spec/integration/type_test.rb b/spec/integration/type_test.rb new file mode 100644 index 0000000000..87e6b2c7ed --- /dev/null +++ b/spec/integration/type_test.rb @@ -0,0 +1,41 @@ +require File.join(File.dirname(__FILE__), 'spec_helper.rb') + +describe "Supported types" do + def create_items_table_with_column(name, type) + INTEGRATION_DB.create_table!(:items){column name, type} + INTEGRATION_DB[:items] + end + + specify "should support NULL correctly" do + ds = create_items_table_with_column(:number, :integer) + ds.insert(:number => nil) + ds.all.should == [{:number=>nil}] + end + + specify "should support integer type" do + ds = create_items_table_with_column(:number, :integer) + ds.insert(:number => 2) + ds.all.should == [{:number=>2}] + end + + specify "should support varchar type" do + ds = create_items_table_with_column(:name, 'varchar(255)'.lit) + ds.insert(:name => 'Test User') + ds.all.should == [{:name=>'Test User'}] + end + + specify "should support date type" do + ds = create_items_table_with_column(:dat, :date) + d = Date.today + ds.insert(:dat => d) + ds.first[:dat].to_s.should == d.to_s + end + + specify "should support time type" do + ds = create_items_table_with_column(:tim, :time) + t = Time.now + ds.insert(:tim => t) + x = ds.first[:tim] + [t.strftime('%H:%M:%S'), t.iso8601].should include(x.respond_to?(:strftime) ? x.strftime('%H:%M:%S') : x.to_s) + end +end diff --git a/spec/spec_config.rb.example b/spec/spec_config.rb.example index 77e748d274..80d9da9a12 100644 --- a/spec/spec_config.rb.example +++ b/spec/spec_config.rb.example @@ -1,4 +1,5 @@ # database objects for running adapter specs +# ADO_DB = Sequel.connect(:adapter => 'ado', :driver => "{Microsoft Access Driver (*.mdb)}; DBQ=c:\\Nwind.mdb") # INFORMIX_DB = Sequel.connect('informix://localhost/mydb') # INTEGRATION_URL = 'sqlite:/' # MYSQL_USER = 'root' @@ -6,5 +7,4 @@ # MYSQL_SOCKET_FILE = '/tmp/mysql.sock' # ORACLE_DB = Sequel.connect('oracle://hr:hr@localhost/XE') # POSTGRES_URL = 'postgres://postgres:postgres@localhost:5432/reality_spec' -# SQLITE_DB = Sequel.connect('sqlite:/') -# ADO_DB = Sequel.connect(:adapter => 'ado', :driver => "{Microsoft Access Driver (*.mdb)}; DBQ=c:\\Nwind.mdb") +# SQLITE_URL = 'sqlite:/'