Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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...
commit f8aa8a3a44aa4b49814ab335e449522ddc1d12e5 1 parent 332883a
Jeremy Evans authored July 24, 2008
2  CHANGELOG
... ...
@@ -1,5 +1,7 @@
1 1
 === HEAD
2 2
 
  3
+* Enable full support for SQLite-JDBC using the JDBC adapter (jeremyevans)
  4
+
3 5
 * Minor changes to allow for full Ruby 1.9 compatibility (jeremyevans)
4 6
 
5 7
 * Make Database#disconnect work for the ADO adapter (spicyj)
34  lib/sequel_core/adapters/jdbc.rb
@@ -7,22 +7,29 @@ module JavaSQL; include_package 'java.sql'; end
7 7
     DATABASE_SETUP = {:postgresql=>proc do |db|
8 8
         require 'sequel_core/adapters/jdbc/postgresql'
9 9
         db.extend(Sequel::JDBC::Postgres::DatabaseMethods)
10  
-        begin
11  
-          require 'jdbc/postgres'
12  
-        rescue LoadError
13  
-          # jdbc-postgres gem not used, hopefully the user has the
14  
-          # PostgreSQL-JDBC .jar in their CLASSPATH
15  
-        end
  10
+        JDBC.load_gem('postgres')
16 11
         org.postgresql.Driver
17 12
       end,
18  
-      :mysql=>proc{com.mysql.jdbc.Driver},
19  
-      :sqlite=>proc{org.sqlite.JDBC},
  13
+      :mysql=>proc do |db|
  14
+        JDBC.load_gem('mysql')
  15
+        com.mysql.jdbc.Driver
  16
+      end,
  17
+      :sqlite=>proc do |db|
  18
+        require 'sequel_core/adapters/jdbc/sqlite'
  19
+        db.extend(Sequel::JDBC::SQLite::DatabaseMethods)
  20
+        JDBC.load_gem('sqlite3')
  21
+        org.sqlite.JDBC
  22
+      end,
20 23
       :oracle=>proc{oracle.jdbc.driver.OracleDriver},
21 24
       :sqlserver=>proc{com.microsoft.sqlserver.jdbc.SQLServerDriver}
22 25
     }
23 26
     
24  
-    def self.load_driver(driver)
25  
-      JavaLang::Class.forName(driver)
  27
+    def self.load_gem(name)
  28
+      begin
  29
+        require "jdbc/#{name}"
  30
+      rescue LoadError
  31
+        # jdbc gem not used, hopefully the user has the .jar in their CLASSPATH
  32
+      end
26 33
     end
27 34
 
28 35
     class Database < Sequel::Database
@@ -57,7 +64,7 @@ def execute(sql)
57 64
           stmt = conn.createStatement
58 65
           begin
59 66
             yield stmt.executeQuery(sql)
60  
-          rescue NativeException => e
  67
+          rescue NativeException, JavaSQL::SQLException => e
61 68
             raise Error, e.message
62 69
           ensure
63 70
             stmt.close
@@ -71,7 +78,7 @@ def execute_ddl(sql)
71 78
           stmt = conn.createStatement
72 79
           begin
73 80
             stmt.execute(sql)
74  
-          rescue NativeException => e
  81
+          rescue NativeException, JavaSQL::SQLException => e
75 82
             raise Error, e.message
76 83
           ensure
77 84
             stmt.close
@@ -85,7 +92,7 @@ def execute_dui(sql)
85 92
           stmt = conn.createStatement
86 93
           begin
87 94
             stmt.executeUpdate(sql)
88  
-          rescue NativeException => e
  95
+          rescue NativeException, JavaSQL::SQLException => e
89 96
             raise Error, e.message
90 97
           ensure
91 98
             stmt.close
@@ -101,6 +108,7 @@ def uri
101 108
         ur = @opts[:uri] || @opts[:url] || @opts[:database]
102 109
         ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}"
103 110
       end
  111
+      alias url uri
104 112
       
105 113
       private
106 114
       
72  lib/sequel_core/adapters/jdbc/sqlite.rb
... ...
@@ -0,0 +1,72 @@
  1
+require 'sequel_core/adapters/shared/sqlite'
  2
+
  3
+module Sequel
  4
+  module JDBC
  5
+    module SQLite
  6
+      module DatabaseMethods
  7
+        include Sequel::SQLite::DatabaseMethods
  8
+        
  9
+        def dataset(opts=nil)
  10
+          Sequel::JDBC::SQLite::Dataset.new(self, opts)
  11
+        end
  12
+        
  13
+        def execute_insert(sql)
  14
+          begin
  15
+            log_info(sql)
  16
+            @pool.hold do |conn|
  17
+              stmt = conn.createStatement
  18
+              begin
  19
+                stmt.executeUpdate(sql)
  20
+                rs = stmt.executeQuery('SELECT last_insert_rowid()')
  21
+                rs.next
  22
+                rs.getInt(1)
  23
+              rescue NativeException, JavaSQL::SQLException => e
  24
+                raise Error, e.message
  25
+              ensure
  26
+                stmt.close
  27
+              end
  28
+            end
  29
+          rescue NativeException, JavaSQL::SQLException => e
  30
+            raise Error, "#{sql}\r\n#{e.message}"
  31
+          end
  32
+        end
  33
+        
  34
+        def transaction
  35
+          @pool.hold do |conn|
  36
+            @transactions ||= []
  37
+            return yield(conn) if @transactions.include?(Thread.current)
  38
+            stmt = conn.createStatement
  39
+            begin
  40
+              log_info(Sequel::Database::SQL_BEGIN)
  41
+              stmt.execute(Sequel::Database::SQL_BEGIN)
  42
+              @transactions << Thread.current
  43
+              yield(conn)
  44
+            rescue Exception => e
  45
+              log_info(Sequel::Database::SQL_ROLLBACK)
  46
+              stmt.execute(Sequel::Database::SQL_ROLLBACK)
  47
+              raise e unless Error::Rollback === e
  48
+            ensure
  49
+              unless e
  50
+                log_info(Sequel::Database::SQL_COMMIT)
  51
+                stmt.execute(Sequel::Database::SQL_COMMIT)
  52
+              end
  53
+              stmt.close
  54
+              @transactions.delete(Thread.current)
  55
+            end
  56
+          end
  57
+        end
  58
+        
  59
+        private
  60
+        
  61
+        def connection_pool_default_options
  62
+          o = super
  63
+          uri == 'jdbc:sqlite::memory:' ? o.merge(:max_connections=>1) : o
  64
+        end
  65
+      end
  66
+    
  67
+      class Dataset < JDBC::Dataset
  68
+        include Sequel::SQLite::DatasetMethods
  69
+      end
  70
+    end
  71
+  end
  72
+end
146  lib/sequel_core/adapters/shared/sqlite.rb
... ...
@@ -0,0 +1,146 @@
  1
+module Sequel
  2
+  module SQLite
  3
+    module DatabaseMethods
  4
+      AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze
  5
+      SCHEMA_TYPE_RE = /\A(\w+)\((\d+)\)\z/
  6
+      SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze
  7
+      TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
  8
+      TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
  9
+      
  10
+      def alter_table_sql(table, op)
  11
+        case op[:op]
  12
+        when :add_column
  13
+          super
  14
+        when :add_index
  15
+          index_definition_sql(table, op)
  16
+        when :drop_column
  17
+          columns_str = (schema_parse_table(table, {}).map{|c| c[0]} - Array(op[:name])).join(",")
  18
+          ["BEGIN TRANSACTION",
  19
+           "CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})",
  20
+           "INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table}",
  21
+           "DROP TABLE #{table}",
  22
+           "CREATE TABLE #{table}(#{columns_str})",
  23
+           "INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup",
  24
+           "DROP TABLE #{table}_backup",
  25
+           "COMMIT"]
  26
+        else
  27
+          raise Error, "Unsupported ALTER TABLE operation"
  28
+        end
  29
+      end
  30
+      
  31
+      def auto_vacuum
  32
+        AUTO_VACUUM[pragma_get(:auto_vacuum).to_s]
  33
+      end
  34
+      
  35
+      def auto_vacuum=(value)
  36
+        value = AUTO_VACUUM.key(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
  37
+        pragma_set(:auto_vacuum, value)
  38
+      end
  39
+      
  40
+      def pragma_get(name)
  41
+        self["PRAGMA #{name}"].single_value
  42
+      end
  43
+      
  44
+      def pragma_set(name, value)
  45
+        execute_ddl("PRAGMA #{name} = #{value}")
  46
+      end
  47
+      
  48
+      def serial_primary_key_options
  49
+        {:primary_key => true, :type => :integer, :auto_increment => true}
  50
+      end
  51
+      
  52
+      def synchronous
  53
+        SYNCHRONOUS[pragma_get(:synchronous).to_s]
  54
+      end
  55
+      
  56
+      def synchronous=(value)
  57
+        value = SYNCHRONOUS.key(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
  58
+        pragma_set(:synchronous, value)
  59
+      end
  60
+    
  61
+      def tables
  62
+        self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym}
  63
+      end
  64
+      
  65
+      def temp_store
  66
+        TEMP_STORE[pragma_get(:temp_store).to_s]
  67
+      end
  68
+      
  69
+      def temp_store=(value)
  70
+        value = TEMP_STORE.key(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
  71
+        pragma_set(:temp_store, value)
  72
+      end
  73
+      
  74
+      private
  75
+
  76
+      def schema_parse_table(table_name, opts)
  77
+        rows = self["PRAGMA table_info(?)", table_name].collect do |row|
  78
+          row.delete(:cid)
  79
+          row[:column] = row.delete(:name)
  80
+          row[:allow_null] = row.delete(:notnull).to_i == 0 ? 'YES' : 'NO'
  81
+          row[:default] = row.delete(:dflt_value)
  82
+          row[:primary_key] = row.delete(:pk).to_i == 1 ? true : false 
  83
+          row[:db_type] = row.delete(:type)
  84
+          if m = SCHEMA_TYPE_RE.match(row[:db_type])
  85
+            row[:db_type] = m[1]
  86
+            row[:max_chars] = m[2].to_i
  87
+          else
  88
+             row[:max_chars] = nil
  89
+          end
  90
+          row[:numeric_precision] = nil
  91
+          row
  92
+        end
  93
+        schema_parse_rows(rows)
  94
+      end
  95
+
  96
+      def schema_parse_tables(opts)
  97
+        schemas = {}
  98
+        tables.each{|table| schemas[table] = schema_parse_table(table, opts)}
  99
+        schemas
  100
+      end
  101
+    end
  102
+  
  103
+    module DatasetMethods      
  104
+      def complex_expression_sql(op, args)
  105
+        case op
  106
+        when :~, :'!~', :'~*', :'!~*'
  107
+          raise Error, "SQLite does not support pattern matching via regular expressions"
  108
+        when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
  109
+          # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
  110
+          "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
  111
+        else
  112
+          super(op, args)
  113
+        end
  114
+      end
  115
+      
  116
+      def delete(opts = nil)
  117
+        # check if no filter is specified
  118
+        unless (opts && opts[:where]) || @opts[:where]
  119
+          @db.transaction do
  120
+            unfiltered_count = count
  121
+            @db.execute_dui delete_sql(opts)
  122
+            unfiltered_count
  123
+          end
  124
+        else
  125
+          @db.execute_dui delete_sql(opts)
  126
+        end
  127
+      end
  128
+      
  129
+      def insert(*values)
  130
+        @db.execute_insert insert_sql(*values)
  131
+      end
  132
+
  133
+      def insert_sql(*values)
  134
+        if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
  135
+          "INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
  136
+        else
  137
+          super(*values)
  138
+        end
  139
+      end
  140
+      
  141
+      def quoted_identifier(c)
  142
+        "`#{c}`"
  143
+      end
  144
+    end
  145
+  end
  146
+end
185  lib/sequel_core/adapters/sqlite.rb
... ...
@@ -1,8 +1,11 @@
1 1
 require 'sqlite3'
  2
+require 'sequel_core/adapters/shared/sqlite'
2 3
 
3 4
 module Sequel
4 5
   module SQLite
5 6
     class Database < Sequel::Database
  7
+      include ::Sequel::SQLite::DatabaseMethods
  8
+      
6 9
       set_adapter_scheme :sqlite
7 10
       
8 11
       def self.uri_to_options(uri) # :nodoc:
@@ -10,10 +13,6 @@ def self.uri_to_options(uri) # :nodoc:
10 13
       end
11 14
 
12 15
       private_class_method :uri_to_options
13  
-    
14  
-      def serial_primary_key_options
15  
-        {:primary_key => true, :type => :integer, :auto_increment => true}
16  
-      end
17 16
 
18 17
       def connect
19 18
         @opts[:database] = ':memory:' if @opts[:database].blank?
@@ -27,18 +26,12 @@ def connect
27 26
         db
28 27
       end
29 28
       
30  
-      def disconnect
31  
-        @pool.disconnect {|c| c.close}
32  
-      end
33  
-    
34 29
       def dataset(opts = nil)
35 30
         SQLite::Dataset.new(self, opts)
36 31
       end
37 32
       
38  
-      TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
39  
-    
40  
-      def tables
41  
-        self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym}
  33
+      def disconnect
  34
+        @pool.disconnect {|c| c.close}
42 35
       end
43 36
     
44 37
       def execute(sql)
@@ -77,68 +70,6 @@ def execute_select(sql, &block)
77 70
         end
78 71
       end
79 72
       
80  
-      def pragma_get(name)
81  
-        single_value("PRAGMA #{name}")
82  
-      end
83  
-      
84  
-      def pragma_set(name, value)
85  
-        execute("PRAGMA #{name} = #{value}")
86  
-      end
87  
-      
88  
-      AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze
89  
-      
90  
-      def auto_vacuum
91  
-        AUTO_VACUUM[pragma_get(:auto_vacuum)]
92  
-      end
93  
-      
94  
-      def auto_vacuum=(value)
95  
-        value = AUTO_VACUUM.key(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
96  
-        pragma_set(:auto_vacuum, value)
97  
-      end
98  
-      
99  
-      SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze
100  
-      
101  
-      def synchronous
102  
-        SYNCHRONOUS[pragma_get(:synchronous)]
103  
-      end
104  
-      
105  
-      def synchronous=(value)
106  
-        value = SYNCHRONOUS.key(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
107  
-        pragma_set(:synchronous, value)
108  
-      end
109  
-      
110  
-      TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
111  
-      
112  
-      def temp_store
113  
-        TEMP_STORE[pragma_get(:temp_store)]
114  
-      end
115  
-      
116  
-      def temp_store=(value)
117  
-        value = TEMP_STORE.key(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
118  
-        pragma_set(:temp_store, value)
119  
-      end
120  
-      
121  
-      def alter_table_sql(table, op)
122  
-        case op[:op]
123  
-        when :add_column
124  
-          super
125  
-        when :add_index
126  
-          index_definition_sql(table, op)
127  
-        when :drop_column
128  
-          columns_str = (schema_parse_table(table, {}).map {|c| c[0] } - (Array === op[:name] ? op[:name] : [op[:name]])).join(",")
129  
-          sql  = "BEGIN TRANSACTION;\n"
130  
-          sql += "CREATE TEMPORARY TABLE #{table}_backup(#{columns_str});\n"
131  
-          sql += "INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table};\n"
132  
-          sql += "DROP TABLE #{table};\n"
133  
-          sql += "CREATE TABLE #{table}(#{columns_str});\n"
134  
-          sql += "INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup;\n"
135  
-          sql += "DROP TABLE #{table}_backup;\n"
136  
-          sql += "COMMIT;\n"
137  
-        else
138  
-          raise Error, "Unsupported ALTER TABLE operation"
139  
-        end
140  
-      end
141  
-      
142 73
       def transaction(&block)
143 74
         @pool.hold do |conn|
144 75
           if conn.transaction_active?
@@ -163,38 +94,29 @@ def connection_pool_default_options
163 94
         o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
164 95
         o
165 96
       end
166  
-
167  
-      SCHEMA_TYPE_RE = /\A(\w+)\((\d+)\)\z/
168  
-      def schema_parse_table(table_name, opts)
169  
-        rows = self["PRAGMA table_info('#{::SQLite3::Database.quote(table_name.to_s)}')"].collect do |row|
170  
-          row.delete(:cid)
171  
-          row[:column] = row.delete(:name)
172  
-          row[:allow_null] = row.delete(:notnull).to_i == 0 ? 'YES' : 'NO'
173  
-          row[:default] = row.delete(:dflt_value)
174  
-          row[:primary_key] = row.delete(:pk).to_i == 1 ? true : false 
175  
-          row[:db_type] = row.delete(:type)
176  
-          if m = SCHEMA_TYPE_RE.match(row[:db_type])
177  
-            row[:db_type] = m[1]
178  
-            row[:max_chars] = m[2].to_i
179  
-          else
180  
-             row[:max_chars] = nil
181  
-          end
182  
-          row[:numeric_precision] = nil
183  
-          row
184  
-        end
185  
-        schema_parse_rows(rows)
186  
-      end
187  
-
188  
-      def schema_parse_tables(opts)
189  
-        schemas = {}
190  
-        tables.each{|table| schemas[table] = schema_parse_table(table, opts)}
191  
-        schemas
192  
-      end
193 97
     end
194 98
     
195 99
     class Dataset < Sequel::Dataset
196  
-      def quoted_identifier(c)
197  
-        "`#{c}`"
  100
+      include ::Sequel::SQLite::DatasetMethods
  101
+      
  102
+      EXPLAIN = 'EXPLAIN %s'.freeze
  103
+      
  104
+      def explain
  105
+        res = []
  106
+        @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
  107
+        res
  108
+      end
  109
+
  110
+      def fetch_rows(sql)
  111
+        @db.execute_select(sql) do |result|
  112
+          @columns = result.columns.map {|c| c.to_sym}
  113
+          column_count = @columns.size
  114
+          result.each do |values|
  115
+            row = {}
  116
+            column_count.times {|i| row[@columns[i]] = values[i]}
  117
+            yield row
  118
+          end
  119
+        end
198 120
       end
199 121
 
200 122
       def literal(v)
@@ -211,63 +133,6 @@ def literal(v)
211 133
           super
212 134
         end
213 135
       end
214  
-
215  
-      def complex_expression_sql(op, args)
216  
-        case op
217  
-        when :~, :'!~', :'~*', :'!~*'
218  
-          raise Error, "SQLite does not support pattern matching via regular expressions"
219  
-        when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
220  
-          # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
221  
-          "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
222  
-        else
223  
-          super(op, args)
224  
-        end
225  
-      end
226  
-
227  
-      def insert_sql(*values)
228  
-        if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
229  
-          "INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
230  
-        else
231  
-          super(*values)
232  
-        end
233  
-      end
234  
-
235  
-      def fetch_rows(sql, &block)
236  
-        @db.execute_select(sql) do |result|
237  
-          @columns = result.columns.map {|c| c.to_sym}
238  
-          column_count = @columns.size
239  
-          result.each do |values|
240  
-            row = {}
241  
-            column_count.times {|i| row[@columns[i]] = values[i]}
242  
-            block.call(row)
243  
-          end
244  
-        end
245  
-      end
246  
-      
247  
-      def insert(*values)
248  
-        @db.execute_insert insert_sql(*values)
249  
-      end
250  
-    
251  
-      def delete(opts = nil)
252  
-        # check if no filter is specified
253  
-        unless (opts && opts[:where]) || @opts[:where]
254  
-          @db.transaction do
255  
-            unfiltered_count = count
256  
-            @db.execute delete_sql(opts)
257  
-            unfiltered_count
258  
-          end
259  
-        else
260  
-          @db.execute delete_sql(opts)
261  
-        end
262  
-      end
263  
-      
264  
-      EXPLAIN = 'EXPLAIN %s'.freeze
265  
-
266  
-      def explain
267  
-        res = []
268  
-        @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
269  
-        res
270  
-      end
271 136
     end
272 137
   end
273 138
 end
2  lib/sequel_core/database.rb
@@ -174,7 +174,7 @@ def self.uri_to_options(uri) # :nodoc:
174 174
     # or as an array of strings. If an array is given, comments and excessive 
175 175
     # white space are removed. See also Array#to_sql.
176 176
     def <<(sql)
177  
-      execute((Array === sql) ? sql.to_sql : sql)
  177
+      execute_ddl((Array === sql) ? sql.to_sql : sql)
178 178
     end
179 179
     
180 180
     # Returns a dataset from the database. If the first argument is a string,
4  lib/sequel_core/database/schema.rb
@@ -41,7 +41,7 @@ def add_index(table, *args)
41 41
     # See Schema::AlterTableGenerator.
42 42
     def alter_table(name, generator=nil, &block)
43 43
       generator ||= Schema::AlterTableGenerator.new(self, &block)
44  
-      alter_table_sql_list(name, generator.operations).each {|sql| execute_ddl(sql)}
  44
+      alter_table_sql_list(name, generator.operations).flatten.each {|sql| execute_ddl(sql)}
45 45
     end
46 46
     
47 47
     # Creates a table with the columns given in the provided block:
@@ -56,7 +56,7 @@ def alter_table(name, generator=nil, &block)
56 56
     # See Schema::Generator.
57 57
     def create_table(name, generator=nil, &block)
58 58
       generator ||= Schema::Generator.new(self, &block)
59  
-      create_table_sql_list(name, *generator.create_info).each {|sql| execute_ddl(sql)}
  59
+      create_table_sql_list(name, *generator.create_info).flatten.each {|sql| execute_ddl(sql)}
60 60
     end
61 61
     
62 62
     # Forcibly creates a table. If the table already exists it is dropped.
185  spec/adapters/sqlite_spec.rb
... ...
@@ -1,56 +1,46 @@
1 1
 require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2 2
 
3 3
 unless defined?(SQLITE_DB)
4  
-  SQLITE_DB = Sequel.connect('sqlite:/')
  4
+  SQLITE_URL = 'sqlite:/' unless defined? SQLITE_URL
  5
+  SQLITE_DB = Sequel.connect(ENV['SEQUEL_SQLITE_SPEC_DB']||SQLITE_URL)
5 6
 end
6 7
 
7  
-SQLITE_DB.create_table :items do
8  
-  integer :id, :primary_key => true, :auto_increment => true
9  
-  text :name
10  
-  float :value
11  
-end
12  
-SQLITE_DB.create_table :test2 do
13  
-  text :name
14  
-  integer :value
15  
-end
16  
-SQLITE_DB.create_table(:time) {timestamp :t}
17  
-
18 8
 context "An SQLite database" do
19 9
   before do
20  
-    @db = Sequel.connect('sqlite:/')
  10
+    @db = SQLITE_DB
21 11
   end
22  
-  after do
23  
-    @db.disconnect
  12
+
  13
+  if SQLITE_DB.auto_vacuum == :none
  14
+    specify "should support getting pragma values" do
  15
+      @db.pragma_get(:auto_vacuum).to_s.should == '0'
  16
+    end
  17
+    
  18
+    specify "should support setting pragma values" do
  19
+      @db.pragma_set(:auto_vacuum, '1')
  20
+      @db.pragma_get(:auto_vacuum).to_s.should == '1'
  21
+      @db.pragma_set(:auto_vacuum, '2')
  22
+      @db.pragma_get(:auto_vacuum).to_s.should == '2'
  23
+    end
  24
+    
  25
+    specify "should support getting and setting the auto_vacuum pragma" do
  26
+      @db.auto_vacuum = :full
  27
+      @db.auto_vacuum.should == :full
  28
+      @db.auto_vacuum = :incremental
  29
+      @db.auto_vacuum.should == :incremental
  30
+      
  31
+      proc {@db.auto_vacuum = :invalid}.should raise_error(Sequel::Error)
  32
+    end
24 33
   end
25 34
   
26 35
   specify "should provide a list of existing tables" do
27  
-    @db.tables.should == []
28  
-    
29  
-    @db.create_table :testing do
  36
+    @db.drop_table(:testing) rescue nil
  37
+    @db.tables.should be_a_kind_of(Array)
  38
+    @db.tables.should_not include(:testing)
  39
+    @db.create_table! :testing do
30 40
       text :name
31 41
     end
32 42
     @db.tables.should include(:testing)
33 43
   end
34  
-  
35  
-  specify "should support getting pragma values" do
36  
-    @db.pragma_get(:auto_vacuum).should == '0'
37  
-  end
38  
-  
39  
-  specify "should support setting pragma values" do
40  
-    @db.pragma_set(:auto_vacuum, '1')
41  
-    @db.pragma_get(:auto_vacuum).should == '1'
42  
-    @db.pragma_set(:auto_vacuum, '2')
43  
-    @db.pragma_get(:auto_vacuum).should == '2'
44  
-  end
45  
-  
46  
-  specify "should support getting and setting the auto_vacuum pragma" do
47  
-    @db.auto_vacuum = :full
48  
-    @db.auto_vacuum.should == :full
49  
-    @db.auto_vacuum = :incremental
50  
-    @db.auto_vacuum.should == :incremental
51  
-    
52  
-    proc {@db.auto_vacuum = :invalid}.should raise_error(Sequel::Error)
53  
-  end
54 44
 
55 45
   specify "should support getting and setting the synchronous pragma" do
56 46
     @db.synchronous = :off
@@ -74,94 +64,76 @@
74 64
     proc {@db.temp_store = :invalid}.should raise_error(Sequel::Error)
75 65
   end
76 66
   
77  
-  specify "should be able to execute multiple statements at once" do
78  
-    @db.create_table :t do
79  
-      text :name
80  
-    end
81  
-    
82  
-    @db << "insert into t (name) values ('abc');insert into t (name) values ('def')"
83  
-
84  
-    @db[:t].count.should == 2
85  
-    
86  
-    @db[:t].order(:name).map(:name).should == ['abc', 'def']
87  
-  end
88  
-  
89 67
   specify "should be able to execute transactions" do
90 68
     @db.transaction do
91  
-      @db.create_table(:t) {text :name}
  69
+      @db.create_table!(:t) {text :name}
92 70
     end
93 71
     
94  
-    @db.tables.should == [:t]
  72
+    @db.tables.should include(:t)
95 73
 
96 74
     proc {@db.transaction do
97  
-      @db.create_table(:u) {text :name}
  75
+      @db.create_table!(:u) {text :name}
98 76
       raise ArgumentError
99 77
     end}.should raise_error(ArgumentError)
100 78
     # no commit
101  
-    @db.tables.should == [:t]
  79
+    @db.tables.should_not include(:u)
102 80
 
103 81
     proc {@db.transaction do
104  
-      @db.create_table(:v) {text :name}
  82
+      @db.create_table!(:v) {text :name}
105 83
       raise Sequel::Error::Rollback
106 84
     end}.should_not raise_error
107 85
     # no commit
108  
-    @db.tables.should == [:t]
  86
+    @db.tables.should_not include(:r)
109 87
   end
110 88
 
111 89
   specify "should support nested transactions" do
112 90
     @db.transaction do
113 91
       @db.transaction do
114  
-        @db.create_table(:t) {text :name}
  92
+        @db.create_table!(:t) {text :name}
115 93
       end
116 94
     end
117 95
     
118  
-    @db.tables.should == [:t]
  96
+    @db.tables.should include(:t)
119 97
 
120 98
     proc {@db.transaction do
121  
-      @db.create_table(:v) {text :name}
  99
+      @db.create_table!(:v) {text :name}
122 100
       @db.transaction do
123 101
         raise Sequel::Error::Rollback # should roll back the top-level transaction
124 102
       end
125 103
     end}.should_not raise_error
126 104
     # no commit
127  
-    @db.tables.should == [:t]
  105
+    @db.tables.should_not include(:v)
128 106
   end
129 107
   
130 108
   specify "should handle returning inside of transaction by committing" do
131  
-    @db.create_table(:items){text :name}
  109
+    @db.create_table!(:items2){text :name}
132 110
     def @db.ret_commit
133 111
       transaction do
134  
-        self[:items] << {:name => 'abc'}
  112
+        self[:items2] << {:name => 'abc'}
135 113
         return
136  
-        self[:items] << {:name => 'd'}
  114
+        self[:items2] << {:name => 'd'}
137 115
       end
138 116
     end
139  
-    @db[:items].count.should == 0
  117
+    @db[:items2].count.should == 0
140 118
     @db.ret_commit
141  
-    @db[:items].count.should == 1
  119
+    @db[:items2].count.should == 1
142 120
     @db.ret_commit
143  
-    @db[:items].count.should == 2
  121
+    @db[:items2].count.should == 2
144 122
     proc do
145 123
       @db.transaction do
146 124
         raise Interrupt, 'asdf'
147 125
       end
148 126
     end.should raise_error(Interrupt)
149 127
 
150  
-    @db[:items].count.should == 2
151  
-  end
152  
-
153  
-  specify "should provide disconnect functionality" do
154  
-    @db.tables
155  
-    @db.pool.size.should == 1
156  
-    @db.disconnect
157  
-    @db.pool.size.should == 0
  128
+    @db[:items2].count.should == 2
158 129
   end
159 130
 
160 131
   specify "should support timestamps" do
161 132
     t1 = Time.at(Time.now.to_i) #normalize time
162  
-    
163  
-    SQLITE_DB[:time] << {:t => t1}
164  
-    SQLITE_DB[:time].first[:t].should == t1
  133
+    @db.create_table!(:time) {timestamp :t}
  134
+    @db[:time] << {:t => t1}
  135
+    x = @db[:time].first[:t]
  136
+    [t1.iso8601, t1.to_s].should include(x.respond_to?(:iso8601) ? x.iso8601 : x.to_s)
165 137
   end
166 138
   
167 139
   specify "should support sequential primary keys" do
@@ -176,18 +148,9 @@ def @db.ret_commit
176 148
     ]
177 149
   end
178 150
   
179  
-  specify "should catch invalid SQL errors and raise them as Error::InvalidStatement" do
180  
-    proc {@db.execute 'blah blah'}.should raise_error(
181  
-      Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error")
182  
-
183  
-    proc {@db.execute_insert 'blah blah'}.should raise_error(
184  
-      Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error")
185  
-
186  
-    proc {@db.execute_select 'blah blah'}.should raise_error(
187  
-      Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error")
188  
-
189  
-    proc {@db.single_value 'blah blah'}.should raise_error(
190  
-      Sequel::Error::InvalidStatement, "blah blah\r\nnear \"blah\": syntax error")
  151
+  specify "should catch invalid SQL errors and raise them as Error" do
  152
+    proc {@db.execute 'blah blah'}.should raise_error(Sequel::Error)
  153
+    proc {@db.execute_insert 'blah blah'}.should raise_error(Sequel::Error)
191 154
   end
192 155
   
193 156
   specify "should not swallow non-SQLite based exceptions" do
@@ -195,18 +158,23 @@ def @db.ret_commit
195 158
   end
196 159
 
197 160
   specify "should correctly parse the schema" do
198  
-    @db.create_table(:time) {timestamp :t}
199  
-    @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}]]
  161
+    @db.create_table!(:time2) {timestamp :t}
  162
+    @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}]]
200 163
   end
201 164
 
202 165
   specify "should get the schema all database tables if no table name is used" do
203  
-    @db.create_table(:time) {timestamp :t}
204  
-    @db.schema(:time, :reload=>true).should == @db.schema(nil, :reload=>true)[:time]
  166
+    @db.create_table!(:time2) {timestamp :t}
  167
+    @db.schema(:time2, :reload=>true).should == @db.schema(nil, :reload=>true)[:time]
205 168
   end
206 169
 end
207 170
 
208 171
 context "An SQLite dataset" do
209 172
   setup do
  173
+    SQLITE_DB.create_table! :items do
  174
+      integer :id, :primary_key => true, :auto_increment => true
  175
+      text :name
  176
+      float :value
  177
+    end
210 178
     @d = SQLITE_DB[:items]
211 179
     @d.delete # remove all records
212 180
   end
@@ -274,6 +242,11 @@ def @db.ret_commit
274 242
 
275 243
 context "An SQLite dataset" do
276 244
   setup do
  245
+    SQLITE_DB.create_table! :items do
  246
+      integer :id, :primary_key => true, :auto_increment => true
  247
+      text :name
  248
+      float :value
  249
+    end
277 250
     @d = SQLITE_DB[:items]
278 251
     @d.delete # remove all records
279 252
     @d << {:name => 'abc', :value => 1.23}
@@ -282,24 +255,29 @@ def @db.ret_commit
282 255
   end
283 256
   
284 257
   specify "should correctly return avg" do
285  
-    @d.avg(:value).should == ((1.23 + 4.56 + 7.89) / 3).to_s
  258
+    @d.avg(:value).to_s.should == ((1.23 + 4.56 + 7.89) / 3).to_s
286 259
   end
287 260
   
288 261
   specify "should correctly return sum" do
289  
-    @d.sum(:value).should == (1.23 + 4.56 + 7.89).to_s
  262
+    @d.sum(:value).to_s.should == (1.23 + 4.56 + 7.89).to_s
290 263
   end
291 264
   
292 265
   specify "should correctly return max" do
293  
-    @d.max(:value).should == 7.89.to_s
  266
+    @d.max(:value).to_s.should == 7.89.to_s
294 267
   end
295 268
   
296 269
   specify "should correctly return min" do
297  
-    @d.min(:value).should == 1.23.to_s
  270
+    @d.min(:value).to_s.should == 1.23.to_s
298 271
   end
299 272
 end
300 273
 
301 274
 context "SQLite::Dataset#delete" do
302 275
   setup do
  276
+    SQLITE_DB.create_table! :items do
  277
+      integer :id, :primary_key => true, :auto_increment => true
  278
+      text :name
  279
+      float :value
  280
+    end
303 281
     @d = SQLITE_DB[:items]
304 282
     @d.delete # remove all records
305 283
     @d << {:name => 'abc', :value => 1.23}
@@ -327,6 +305,11 @@ def @db.ret_commit
327 305
 
328 306
 context "SQLite::Dataset#update" do
329 307
   setup do
  308
+    SQLITE_DB.create_table! :items do
  309
+      integer :id, :primary_key => true, :auto_increment => true
  310
+      text :name
  311
+      float :value
  312
+    end
330 313
     @d = SQLITE_DB[:items]
331 314
     @d.delete # remove all records
332 315
     @d << {:name => 'abc', :value => 1.23}
@@ -345,12 +328,16 @@ def @db.ret_commit
345 328
 
346 329
 context "SQLite dataset" do
347 330
   setup do
348  
-    SQLITE_DB.create_table :test do
  331
+    SQLITE_DB.create_table! :test do
  332
+      integer :id, :primary_key => true, :auto_increment => true
  333
+      text :name
  334
+      float :value
  335
+    end
  336
+    SQLITE_DB.create_table! :items do
349 337
       integer :id, :primary_key => true, :auto_increment => true
350 338
       text :name
351 339
       float :value
352 340
     end
353  
-
354 341
     @d = SQLITE_DB[:items]
355 342
     @d.delete # remove all records
356 343
     @d << {:name => 'abc', :value => 1.23}
4  spec/integration/dataset_test.rb
@@ -28,13 +28,13 @@
28 28
   end
29 29
 
30 30
   specify "should delete correctly" do
31  
-    @ds.filter(1=>1).delete
  31
+    @ds.filter(1=>1).delete.should == 1
32 32
     sqls_should_be('DELETE FROM items WHERE (1 = 1)')
33 33
     @ds.count.should == 0
34 34
   end
35 35
 
36 36
   specify "should update correctly" do
37  
-    @ds.update(:number=>:number+1)
  37
+    @ds.update(:number=>:number+1).should == 1
38 38
     sqls_should_be('UPDATE items SET number = (number + 1)')
39 39
     @ds.all.should == [{:id=>1, :number=>11}]
40 40
   end
41  spec/integration/type_test.rb
... ...
@@ -0,0 +1,41 @@
  1
+require File.join(File.dirname(__FILE__), 'spec_helper.rb')
  2
+
  3
+describe "Supported types" do
  4
+  def create_items_table_with_column(name, type)
  5
+    INTEGRATION_DB.create_table!(:items){column name, type}
  6
+    INTEGRATION_DB[:items]
  7
+  end
  8
+
  9
+  specify "should support NULL correctly" do
  10
+    ds = create_items_table_with_column(:number, :integer)
  11
+    ds.insert(:number => nil)
  12
+    ds.all.should == [{:number=>nil}]
  13
+  end
  14
+
  15
+  specify "should support integer type" do
  16
+    ds = create_items_table_with_column(:number, :integer)
  17
+    ds.insert(:number => 2)
  18
+    ds.all.should == [{:number=>2}]
  19
+  end
  20
+
  21
+  specify "should support varchar type" do
  22
+    ds = create_items_table_with_column(:name, 'varchar(255)'.lit)
  23
+    ds.insert(:name => 'Test User')
  24
+    ds.all.should == [{:name=>'Test User'}]
  25
+  end
  26
+  
  27
+  specify "should support date type" do
  28
+    ds = create_items_table_with_column(:dat, :date)
  29
+    d = Date.today
  30
+    ds.insert(:dat => d)
  31
+    ds.first[:dat].to_s.should == d.to_s
  32
+  end
  33
+  
  34
+  specify "should support time type" do
  35
+    ds = create_items_table_with_column(:tim, :time)
  36
+    t = Time.now
  37
+    ds.insert(:tim => t)
  38
+    x = ds.first[:tim]
  39
+    [t.strftime('%H:%M:%S'), t.iso8601].should include(x.respond_to?(:strftime) ? x.strftime('%H:%M:%S') : x.to_s)
  40
+  end
  41
+end
4  spec/spec_config.rb.example
... ...
@@ -1,4 +1,5 @@
1 1
 # database objects for running adapter specs
  2
+# ADO_DB = Sequel.connect(:adapter => 'ado', :driver => "{Microsoft Access Driver (*.mdb)}; DBQ=c:\\Nwind.mdb")
2 3
 # INFORMIX_DB = Sequel.connect('informix://localhost/mydb')
3 4
 # INTEGRATION_URL = 'sqlite:/'
4 5
 # MYSQL_USER = 'root'
@@ -6,5 +7,4 @@
6 7
 # MYSQL_SOCKET_FILE = '/tmp/mysql.sock'
7 8
 # ORACLE_DB = Sequel.connect('oracle://hr:hr@localhost/XE')
8 9
 # POSTGRES_URL = 'postgres://postgres:postgres@localhost:5432/reality_spec'
9  
-# SQLITE_DB = Sequel.connect('sqlite:/')
10  
-# ADO_DB = Sequel.connect(:adapter => 'ado', :driver => "{Microsoft Access Driver (*.mdb)}; DBQ=c:\\Nwind.mdb")
  10
+# SQLITE_URL = 'sqlite:/'

0 notes on commit f8aa8a3

Please sign in to comment.
Something went wrong with that request. Please try again.