sequel_core 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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