sequel_core 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -1,14 +1,45 @@
1
- require 'uri'
1
+ require 'sequel_core/database/schema'
2
2
 
3
3
  module Sequel
4
+ # Array of all databases to which Sequel has connected. If you are
5
+ # developing an application that can connect to an arbitrary number of
6
+ # databases, delete the database objects from this or they will not get
7
+ # garbage collected.
4
8
  DATABASES = []
9
+
5
10
  # A Database object represents a virtual connection to a database.
6
11
  # The Database class is meant to be subclassed by database adapters in order
7
12
  # to provide the functionality needed for executing queries.
8
13
  class Database
14
+ include Schema::SQL
15
+
16
+ # Array of supported database adapters
9
17
  ADAPTERS = %w'ado db2 dbi informix jdbc mysql odbc odbc_mssql openbase oracle postgres sqlite'.collect{|x| x.to_sym}
10
- attr_reader :opts, :pool
11
- attr_accessor :logger
18
+
19
+ SQL_BEGIN = 'BEGIN'.freeze
20
+ SQL_COMMIT = 'COMMIT'.freeze
21
+ SQL_ROLLBACK = 'ROLLBACK'.freeze
22
+
23
+ # Hash of adapters that have been used
24
+ @@adapters = Hash.new
25
+
26
+ # Whether to use the single threaded connection pool by default
27
+ @@single_threaded = false
28
+
29
+ # Whether to quote identifiers (columns and tables) by default
30
+ @@quote_identifiers = true
31
+
32
+ # Array of SQL loggers to use for this database
33
+ attr_accessor :loggers
34
+
35
+ # The options for this database
36
+ attr_reader :opts
37
+
38
+ # The connection pool for this database
39
+ attr_reader :pool
40
+
41
+ # Whether to quote identifiers (columns and tables) for this database
42
+ attr_writer :quote_identifiers
12
43
 
13
44
  # Constructs a new instance of a database connection with the specified
14
45
  # options hash.
@@ -17,71 +48,182 @@ module Sequel
17
48
  def initialize(opts = {}, &block)
18
49
  @opts = opts
19
50
 
20
- # Determine if the DB is single threaded or multi threaded
51
+ @quote_identifiers = opts[:quote_identifiers] || @@quote_identifiers
21
52
  @single_threaded = opts[:single_threaded] || @@single_threaded
22
- # Construct pool
23
- if @single_threaded
24
- @pool = SingleThreadedPool.new(&block)
25
- else
26
- @pool = ConnectionPool.new(opts[:max_connections] || 4, &block)
27
- end
28
- @pool.connection_proc = block || proc {connect}
53
+ @schemas = nil
54
+ @pool = (@single_threaded ? SingleThreadedPool : ConnectionPool).new(connection_pool_default_options.merge(opts), &block)
55
+ @pool.connection_proc = proc {connect} unless block
29
56
 
30
- @logger = opts[:logger]
57
+ @loggers = Array(opts[:logger]) + Array(opts[:loggers])
31
58
  ::Sequel::DATABASES.push(self)
32
59
  end
33
60
 
34
- # Connects to the database. This method should be overriden by descendants.
35
- def connect
36
- raise NotImplementedError, "#connect should be overriden by adapters"
61
+ ### Class Methods ###
62
+
63
+ # The Database subclass for the given adapter scheme.
64
+ # Raises Sequel::Error::AdapterNotFound if the adapter
65
+ # could not be loaded.
66
+ def self.adapter_class(scheme)
67
+ scheme = scheme.to_s.gsub('-', '_').to_sym
68
+
69
+ if (klass = @@adapters[scheme]).nil?
70
+ # attempt to load the adapter file
71
+ begin
72
+ require "sequel_core/adapters/#{scheme}"
73
+ rescue LoadError => e
74
+ raise Error::AdapterNotFound, "Could not load #{scheme} adapter:\n #{e.message}"
75
+ end
76
+
77
+ # make sure we actually loaded the adapter
78
+ if (klass = @@adapters[scheme]).nil?
79
+ raise Error::AdapterNotFound, "Could not load #{scheme} adapter"
80
+ end
81
+ end
82
+ return klass
83
+ end
84
+
85
+ # Returns the scheme for the Database class.
86
+ def self.adapter_scheme
87
+ @scheme
37
88
  end
38
89
 
39
- # Disconnects from the database. This method should be overriden by
40
- # descendants.
41
- def disconnect
42
- raise NotImplementedError, "#disconnect should be overriden by adapters"
90
+ # Connects to a database. See Sequel.connect.
91
+ def self.connect(conn_string, opts = nil, &block)
92
+ if conn_string.is_a?(String)
93
+ uri = URI.parse(conn_string)
94
+ scheme = uri.scheme
95
+ scheme = :dbi if scheme =~ /^dbi-(.+)/
96
+ c = adapter_class(scheme)
97
+ opts = c.uri_to_options(uri).merge(opts || {})
98
+ else
99
+ opts = conn_string.merge(opts || {})
100
+ c = adapter_class(opts[:adapter] || opts['adapter'])
101
+ end
102
+ # process opts a bit
103
+ opts = opts.inject({}) do |m, kv| k, v = *kv
104
+ k = :user if k.to_s == 'username'
105
+ m[k.to_sym] = v
106
+ m
107
+ end
108
+ if block
109
+ begin
110
+ yield(db = c.new(opts))
111
+ ensure
112
+ db.disconnect if db
113
+ ::Sequel::DATABASES.delete(db)
114
+ end
115
+ nil
116
+ else
117
+ c.new(opts)
118
+ end
119
+ end
120
+
121
+ # Sets the default quote_identifiers mode for new databases.
122
+ # See Sequel.quote_identifiers=.
123
+ def self.quote_identifiers=(value)
124
+ @@quote_identifiers = value
125
+ end
126
+
127
+ # Sets the default single_threaded mode for new databases.
128
+ # See Sequel.single_threaded=.
129
+ def self.single_threaded=(value)
130
+ @@single_threaded = value
131
+ end
132
+
133
+ # Converts a uri to an options hash. These options are then passed
134
+ # to a newly created database object.
135
+ def self.uri_to_options(uri)
136
+ if uri.is_a?(String)
137
+ uri = URI.parse(uri)
138
+ end
139
+ # special case for sqlite
140
+ if uri.scheme == 'sqlite'
141
+ {
142
+ :user => uri.user,
143
+ :password => uri.password,
144
+ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}"
145
+ }
146
+ else
147
+ {
148
+ :user => uri.user,
149
+ :password => uri.password,
150
+ :host => uri.host,
151
+ :port => uri.port,
152
+ :database => (m = /\/(.*)/.match(uri.path)) && (m[1])
153
+ }
154
+ end
43
155
  end
44
156
 
45
- # Returns true if the database is using a multi-threaded connection pool.
46
- def multi_threaded?
47
- !@single_threaded
157
+ ### Private Class Methods ###
158
+
159
+ # Sets the adapter scheme for the Database class. Call this method in
160
+ # descendnants of Database to allow connection using a URL. For example the
161
+ # following:
162
+ #
163
+ # class Sequel::MyDB::Database < Sequel::Database
164
+ # set_adapter_scheme :mydb
165
+ # ...
166
+ # end
167
+ #
168
+ # would allow connection using:
169
+ #
170
+ # Sequel.connect('mydb://user:password@dbserver/mydb')
171
+ def self.set_adapter_scheme(scheme) # :nodoc:
172
+ @scheme = scheme
173
+ @@adapters[scheme.to_sym] = self
48
174
  end
175
+ metaprivate :set_adapter_scheme
49
176
 
50
- # Returns true if the database is using a single-threaded connection pool.
51
- def single_threaded?
52
- @single_threaded
177
+ ### Instance Methods ###
178
+
179
+ # Executes the supplied SQL statement. The SQL can be supplied as a string
180
+ # or as an array of strings. If an array is given, comments and excessive
181
+ # white space are removed. See also Array#to_sql.
182
+ def <<(sql)
183
+ execute((Array === sql) ? sql.to_sql : sql)
53
184
  end
54
185
 
55
- # Returns the URI identifying the database.
56
- def uri
57
- uri = URI::Generic.new(
58
- self.class.adapter_scheme.to_s,
59
- nil,
60
- @opts[:host],
61
- @opts[:port],
62
- nil,
63
- "/#{@opts[:database]}",
64
- nil,
65
- nil,
66
- nil
67
- )
68
- uri.user = @opts[:user]
69
- uri.password = @opts[:password] if uri.user
70
- uri.to_s
186
+ # Returns a dataset from the database. If the first argument is a string,
187
+ # the method acts as an alias for Database#fetch, returning a dataset for
188
+ # arbitrary SQL:
189
+ #
190
+ # DB['SELECT * FROM items WHERE name = ?', my_name].print
191
+ #
192
+ # Otherwise, acts as an alias for Database#from, setting the primary
193
+ # table for the dataset:
194
+ #
195
+ # DB[:items].sql #=> "SELECT * FROM items"
196
+ def [](*args, &block)
197
+ (String === args.first) ? fetch(*args, &block) : from(*args, &block)
198
+ end
199
+
200
+ # Connects to the database. This method should be overridden by descendants.
201
+ def connect
202
+ raise NotImplementedError, "#connect should be overridden by adapters"
71
203
  end
72
- alias url uri # Because I don't care much for the semantic difference.
73
204
 
74
205
  # Returns a blank dataset
75
206
  def dataset
76
207
  ds = Sequel::Dataset.new(self)
77
208
  end
78
209
 
210
+ # Disconnects from the database. This method should be overridden by
211
+ # descendants.
212
+ def disconnect
213
+ raise NotImplementedError, "#disconnect should be overridden by adapters"
214
+ end
215
+
216
+ # Executes the given SQL. This method should be overridden in descendants.
217
+ def execute(sql)
218
+ raise NotImplementedError, "#execute should be overridden by adapters"
219
+ end
220
+
79
221
  # Fetches records for an arbitrary SQL statement. If a block is given,
80
222
  # it is used to iterate over the records:
81
223
  #
82
- # DB.fetch('SELECT * FROM items') {|r| p r}
224
+ # DB.fetch('SELECT * FROM items'){|r| p r}
83
225
  #
84
- # If a block is not given, the method returns a dataset instance:
226
+ # The method returns a dataset instance:
85
227
  #
86
228
  # DB.fetch('SELECT * FROM items').print
87
229
  #
@@ -89,29 +231,15 @@ module Sequel
89
231
  # injection:
90
232
  #
91
233
  # DB.fetch('SELECT * FROM items WHERE name = ?', my_name).print
92
- #
93
- # A short-hand form for Database#fetch is Database#[]:
94
- #
95
- # DB['SELECT * FROM items'].each {|r| p r}
96
- #
97
234
  def fetch(sql, *args, &block)
98
235
  ds = dataset
99
236
  sql = sql.gsub('?') {|m| ds.literal(args.shift)}
100
- if block
101
- ds.fetch_rows(sql, &block)
102
- else
103
- ds.opts[:sql] = sql
104
- ds
105
- end
237
+ ds.opts[:sql] = sql
238
+ ds.fetch_rows(sql, &block) if block
239
+ ds
106
240
  end
107
241
  alias_method :>>, :fetch
108
242
 
109
- # Converts a query block into a dataset. For more information see
110
- # Dataset#query.
111
- def query(&block)
112
- dataset.query(&block)
113
- end
114
-
115
243
  # Returns a new dataset with the from method invoked. If a block is given,
116
244
  # it is used as a filter on the dataset.
117
245
  def from(*args, &block)
@@ -119,24 +247,6 @@ module Sequel
119
247
  block ? ds.filter(&block) : ds
120
248
  end
121
249
 
122
- # Returns a new dataset with the select method invoked.
123
- def select(*args); dataset.select(*args); end
124
-
125
- # Returns a dataset from the database. If the first argument is a string,
126
- # the method acts as an alias for Database#fetch, returning a dataset for
127
- # arbitrary SQL:
128
- #
129
- # DB['SELECT * FROM items WHERE name = ?', my_name].print
130
- #
131
- # Otherwise, the dataset returned has its from option set to the given
132
- # arguments:
133
- #
134
- # DB[:items].sql #=> "SELECT * FROM items"
135
- #
136
- def [](*args)
137
- (String === args.first) ? fetch(*args) : from(*args)
138
- end
139
-
140
250
  # Returns a single value from the database, e.g.:
141
251
  #
142
252
  # # SELECT 1
@@ -148,186 +258,89 @@ module Sequel
148
258
  dataset.get(expr)
149
259
  end
150
260
 
151
- # Raises a Sequel::Error::NotImplemented. This method is overriden in descendants.
152
- def execute(sql)
153
- raise NotImplementedError, "#execute should be overriden by adapters"
154
- end
155
-
156
- # Executes the supplied SQL statement. The SQL can be supplied as a string
157
- # or as an array of strings. If an array is give, comments and excessive
158
- # white space are removed. See also Array#to_sql.
159
- def <<(sql); execute((Array === sql) ? sql.to_sql : sql); end
160
-
161
- # Acquires a database connection, yielding it to the passed block.
162
- def synchronize(&block)
163
- @pool.hold(&block)
261
+ # Returns a string representation of the database object including the
262
+ # class name and the connection URI (or the opts if the URI
263
+ # cannot be constructed).
264
+ def inspect
265
+ "#<#{self.class}: #{(uri rescue opts).inspect}>"
164
266
  end
165
267
 
166
- # Returns true if there is a database connection
167
- def test_connection
168
- @pool.hold {|conn|}
169
- true
170
- end
171
-
172
- include Schema::SQL
173
-
174
- # default serial primary key definition. this should be overriden for each adapter.
175
- def serial_primary_key_options
176
- {:primary_key => true, :type => :integer, :auto_increment => true}
177
- end
178
-
179
- # Creates a table. The easiest way to use this method is to provide a
180
- # block:
181
- # DB.create_table :posts do
182
- # primary_key :id, :serial
183
- # column :title, :text
184
- # column :content, :text
185
- # index :title
186
- # end
187
- def create_table(name, &block)
188
- g = Schema::Generator.new(self, &block)
189
- create_table_sql_list(name, *g.create_info).each {|sql| execute(sql)}
190
- end
191
-
192
- # Forcibly creates a table. If the table already exists it is dropped.
193
- def create_table!(name, &block)
194
- drop_table(name) rescue nil
195
- create_table(name, &block)
196
- end
197
-
198
- # Drops one or more tables corresponding to the given table names.
199
- def drop_table(*names)
200
- names.each {|n| execute(drop_table_sql(n))}
268
+ # Log a message at level info to all loggers. All SQL logging
269
+ # goes through this method.
270
+ def log_info(message)
271
+ @loggers.each{|logger| logger.info(message)}
201
272
  end
202
-
203
- # Renames a table:
204
- #
205
- # DB.tables #=> [:items]
206
- # DB.rename_table :items, :old_items
207
- # DB.tables #=> [:old_items]
208
- def rename_table(*args)
209
- execute(rename_table_sql(*args))
273
+
274
+ # Return the first logger or nil if no loggers are being used.
275
+ # Should only be used for backwards compatibility.
276
+ def logger
277
+ @loggers.first
210
278
  end
211
-
212
- # Alters the given table with the specified block. Here are the currently
213
- # available operations:
214
- #
215
- # DB.alter_table :items do
216
- # add_column :category, :text, :default => 'ruby'
217
- # drop_column :category
218
- # rename_column :cntr, :counter
219
- # set_column_type :value, :float
220
- # set_column_default :value, :float
221
- # add_index [:group, :category]
222
- # drop_index [:group, :category]
223
- # end
224
- #
225
- # Note that #add_column accepts all the options available for column
226
- # definitions using create_table, and #add_index accepts all the options
227
- # available for index definition.
228
- def alter_table(name, &block)
229
- g = Schema::AlterTableGenerator.new(self, &block)
230
- alter_table_sql_list(name, g.operations).each {|sql| execute(sql)}
279
+
280
+ # Replace the array of loggers with the given logger(s).
281
+ def logger=(logger)
282
+ @loggers = Array(logger)
231
283
  end
232
-
233
- # Adds a column to the specified table. This method expects a column name,
234
- # a datatype and optionally a hash with additional constraints and options:
235
- #
236
- # DB.add_column :items, :name, :text, :unique => true, :null => false
237
- # DB.add_column :items, :category, :text, :default => 'ruby'
238
- def add_column(table, *args)
239
- alter_table(table) {add_column(*args)}
284
+
285
+ # Returns true unless the database is using a single-threaded connection pool.
286
+ def multi_threaded?
287
+ !@single_threaded
240
288
  end
241
289
 
242
- # Removes a column from the specified table:
243
- #
244
- # DB.drop_column :items, :category
245
- def drop_column(table, *args)
246
- alter_table(table) {drop_column(*args)}
290
+ # Returns a dataset modified by the given query block. See Dataset#query.
291
+ def query(&block)
292
+ dataset.query(&block)
247
293
  end
248
294
 
249
- # Renames a column in the specified table. This method expects the current
250
- # column name and the new column name:
251
- #
252
- # DB.rename_column :items, :cntr, :counter
253
- def rename_column(table, *args)
254
- alter_table(table) {rename_column(*args)}
295
+ # Returns true if the database quotes identifiers.
296
+ def quote_identifiers?
297
+ @quote_identifiers
255
298
  end
256
299
 
257
- # Set the data type for the given column in the given table:
258
- #
259
- # DB.set_column_type :items, :price, :float
260
- def set_column_type(table, *args)
261
- alter_table(table) {set_column_type(*args)}
300
+ # Returns a new dataset with the select method invoked.
301
+ def select(*args)
302
+ dataset.select(*args)
262
303
  end
263
304
 
264
- # Sets the default value for the given column in the given table:
265
- #
266
- # DB.set_column_default :items, :category, 'perl!'
267
- def set_column_default(table, *args)
268
- alter_table(table) {set_column_default(*args)}
305
+ # Default serial primary key options.
306
+ def serial_primary_key_options
307
+ {:primary_key => true, :type => :integer, :auto_increment => true}
269
308
  end
270
309
 
271
- # Adds an index to a table for the given columns:
272
- #
273
- # DB.add_index :posts, :title
274
- # DB.add_index :posts, [:author, :title], :unique => true
275
- def add_index(table, *args)
276
- alter_table(table) {add_index(*args)}
310
+ # Returns true if the database is using a single-threaded connection pool.
311
+ def single_threaded?
312
+ @single_threaded
277
313
  end
278
314
 
279
- # Removes an index for the given table and column/s:
280
- #
281
- # DB.drop_index :posts, :title
282
- # DB.drop_index :posts, [:author, :title]
283
- def drop_index(table, columns)
284
- alter_table(table) {drop_index(columns)}
315
+ # Acquires a database connection, yielding it to the passed block.
316
+ def synchronize(&block)
317
+ @pool.hold(&block)
285
318
  end
286
-
287
- # Returns true if the given table exists.
319
+
320
+ # Returns true if a table with the given name exists.
288
321
  def table_exists?(name)
289
- if respond_to?(:tables)
290
- tables.include?(name.to_sym)
291
- else
292
- from(name).first
293
- true
322
+ begin
323
+ if respond_to?(:tables)
324
+ tables.include?(name.to_sym)
325
+ else
326
+ from(name).first
327
+ true
328
+ end
329
+ rescue
330
+ false
294
331
  end
295
- rescue
296
- false
297
- end
298
-
299
- # Creates a view based on a dataset or an SQL string:
300
- #
301
- # DB.create_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
302
- # DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
303
- def create_view(name, source)
304
- source = source.sql if source.is_a?(Dataset)
305
- execute("CREATE VIEW #{name} AS #{source}")
306
- end
307
-
308
- # Creates a view, replacing it if it already exists:
309
- #
310
- # DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
311
- # DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
312
- def create_or_replace_view(name, source)
313
- source = source.sql if source.is_a?(Dataset)
314
- execute("CREATE OR REPLACE VIEW #{name} AS #{source}")
315
332
  end
316
333
 
317
- # Drops a view:
318
- #
319
- # DB.drop_view(:cheap_items)
320
- def drop_view(name)
321
- execute("DROP VIEW #{name}")
334
+ # Attempts to acquire a database connection. Returns true if successful.
335
+ # Will probably raise an error if unsuccessful.
336
+ def test_connection
337
+ synchronize{|conn|}
338
+ true
322
339
  end
323
340
 
324
- SQL_BEGIN = 'BEGIN'.freeze
325
- SQL_COMMIT = 'COMMIT'.freeze
326
- SQL_ROLLBACK = 'ROLLBACK'.freeze
327
-
328
341
  # A simple implementation of SQL transactions. Nested transactions are not
329
342
  # supported - calling #transaction within a transaction will reuse the
330
- # current transaction. May be overridden for databases that support nested
343
+ # current transaction. Should be overridden for databases that support nested
331
344
  # transactions.
332
345
  def transaction
333
346
  @pool.hold do |conn|
@@ -335,128 +348,98 @@ module Sequel
335
348
  if @transactions.include? Thread.current
336
349
  return yield(conn)
337
350
  end
351
+ log_info(SQL_BEGIN)
338
352
  conn.execute(SQL_BEGIN)
339
353
  begin
340
354
  @transactions << Thread.current
341
- result = yield(conn)
342
- conn.execute(SQL_COMMIT)
343
- result
344
- rescue => e
355
+ yield(conn)
356
+ rescue Exception => e
357
+ log_info(SQL_ROLLBACK)
345
358
  conn.execute(SQL_ROLLBACK)
346
359
  raise e unless Error::Rollback === e
347
360
  ensure
361
+ unless e
362
+ log_info(SQL_COMMIT)
363
+ conn.execute(SQL_COMMIT)
364
+ end
348
365
  @transactions.delete(Thread.current)
349
366
  end
350
367
  end
351
368
  end
352
369
 
353
- # Returns a string representation of the database object including the
354
- # class name and the connection URI.
355
- def inspect
356
- "#<#{self.class}: #{(uri rescue opts).inspect}>"
357
- end
358
-
359
- @@adapters = Hash.new
360
-
361
- class << self
362
- private
363
- # Sets the adapter scheme for the Database class. Call this method in
364
- # descendnants of Database to allow connection using a URL. For example the
365
- # following:
366
- # class DB2::Database < Sequel::Database
367
- # set_adapter_scheme :db2
368
- # ...
369
- # end
370
- # would allow connection using:
371
- # Sequel.open('db2://user:password@dbserver/mydb')
372
- def set_adapter_scheme(scheme)
373
- @scheme = scheme
374
- @@adapters[scheme.to_sym] = self
375
- end
376
- end
377
-
378
- # Returns the scheme for the Database class.
379
- def self.adapter_scheme
380
- @scheme
381
- end
382
-
383
- # Converts a uri to an options hash. These options are then passed
384
- # to a newly created database object.
385
- def self.uri_to_options(uri)
386
- if uri.is_a?(String)
387
- uri = URI.parse(uri)
388
- end
389
- {
390
- :user => uri.user,
391
- :password => uri.password,
392
- :host => uri.host,
393
- :port => uri.port,
394
- :database => (uri.path =~ /\/(.*)/) && ($1)
395
- }
396
- end
397
-
398
- def self.adapter_class(scheme)
399
- scheme = scheme.to_sym
400
-
401
- if (klass = @@adapters[scheme]).nil?
402
- # attempt to load the adapter file
403
- begin
404
- require File.join(File.dirname(__FILE__), "adapters/#{scheme}")
405
- rescue LoadError => e
406
- raise Error::AdapterNotFound, "Could not load #{scheme} adapter:\n #{e.message}"
370
+ # Typecast the value to the given column_type. Can be overridden in
371
+ # adapters to support database specific column types.
372
+ def typecast_value(column_type, value)
373
+ return nil if value.nil?
374
+ case column_type
375
+ when :integer
376
+ Integer(value)
377
+ when :string
378
+ value.to_s
379
+ when :float
380
+ Float(value)
381
+ when :boolean
382
+ case value
383
+ when false, 0, "0", /\Af(alse)?\z/i
384
+ false
385
+ else
386
+ value.blank? ? nil : true
407
387
  end
408
-
409
- # make sure we actually loaded the adapter
410
- if (klass = @@adapters[scheme]).nil?
411
- raise Error::AdapterNotFound, "Could not load #{scheme} adapter"
388
+ when :date
389
+ case value
390
+ when Date
391
+ value
392
+ when DateTime, Time
393
+ Date.new(value.year, value.month, value.day)
394
+ when String
395
+ value.to_date
396
+ else
397
+ raise ArgumentError, "invalid value for Date: #{value.inspect}"
398
+ end
399
+ when :datetime
400
+ case value
401
+ when DateTime
402
+ value
403
+ when Date
404
+ DateTime.new(value.year, value.month, value.day)
405
+ when Time
406
+ DateTime.new(value.year, value.month, value.day, value.hour, value.min, value.sec)
407
+ when String
408
+ value.to_datetime
409
+ else
410
+ raise ArgumentError, "invalid value for DateTime: #{value.inspect}"
412
411
  end
413
- end
414
- return klass
415
- end
416
-
417
- # call-seq:
418
- # Sequel::Database.connect(conn_string)
419
- # Sequel::Database.connect(opts)
420
- # Sequel.connect(conn_string)
421
- # Sequel.connect(opts)
422
- # Sequel.open(conn_string)
423
- # Sequel.open(opts)
424
- #
425
- # Creates a new database object based on the supplied connection string
426
- # and or options. If a URI is used, the URI scheme determines the database
427
- # class used, and the rest of the string specifies the connection options.
428
- # For example:
429
- #
430
- # DB = Sequel.open 'sqlite:///blog.db'
431
- #
432
- # The second form of this method takes an options:
433
- #
434
- # DB = Sequel.open :adapter => :sqlite, :database => 'blog.db'
435
- def self.connect(conn_string, opts = nil)
436
- if conn_string.is_a?(String)
437
- uri = URI.parse(conn_string)
438
- scheme = uri.scheme
439
- scheme = :dbi if scheme =~ /^dbi-(.+)/
440
- c = adapter_class(scheme)
441
- opts = c.uri_to_options(uri).merge(opts || {})
442
412
  else
443
- opts = conn_string.merge(opts || {})
444
- c = adapter_class(opts[:adapter] || opts['adapter'])
413
+ value
445
414
  end
446
- # process opts a bit
447
- opts = opts.inject({}) do |m, kv| k, v = *kv
448
- k = :user if k == 'username'
449
- m[k.to_sym] = v
450
- m
451
- end
452
- c.new(opts)
415
+ end
416
+
417
+ # Returns the URI identifying the database.
418
+ # This method can raise an error if the database used options
419
+ # instead of a connection string.
420
+ def uri
421
+ uri = URI::Generic.new(
422
+ self.class.adapter_scheme.to_s,
423
+ nil,
424
+ @opts[:host],
425
+ @opts[:port],
426
+ nil,
427
+ "/#{@opts[:database]}",
428
+ nil,
429
+ nil,
430
+ nil
431
+ )
432
+ uri.user = @opts[:user]
433
+ uri.password = @opts[:password] if uri.user
434
+ uri.to_s
453
435
  end
436
+ alias_method :url, :uri
454
437
 
455
- @@single_threaded = false
456
-
457
- # Sets the default single_threaded mode for new databases.
458
- def self.single_threaded=(value)
459
- @@single_threaded = value
438
+ private
439
+
440
+ # The default options for the connection pool.
441
+ def connection_pool_default_options
442
+ {}
460
443
  end
461
444
  end
462
445
  end