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.
- data/CHANGELOG +116 -0
- data/COPYING +19 -19
- data/README +83 -32
- data/Rakefile +9 -20
- data/bin/sequel +43 -112
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +257 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
- data/lib/sequel_core/adapters/ado.rb +3 -1
- data/lib/sequel_core/adapters/db2.rb +4 -2
- data/lib/sequel_core/adapters/dbi.rb +127 -113
- data/lib/sequel_core/adapters/informix.rb +4 -2
- data/lib/sequel_core/adapters/jdbc.rb +5 -3
- data/lib/sequel_core/adapters/mysql.rb +112 -46
- data/lib/sequel_core/adapters/odbc.rb +5 -7
- data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
- data/lib/sequel_core/adapters/openbase.rb +3 -1
- data/lib/sequel_core/adapters/oracle.rb +11 -9
- data/lib/sequel_core/adapters/postgres.rb +261 -262
- data/lib/sequel_core/adapters/sqlite.rb +72 -22
- data/lib/sequel_core/connection_pool.rb +140 -73
- data/lib/sequel_core/core_ext.rb +201 -66
- data/lib/sequel_core/core_sql.rb +123 -153
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/database.rb +321 -338
- data/lib/sequel_core/dataset/callback.rb +11 -12
- data/lib/sequel_core/dataset/convenience.rb +213 -240
- data/lib/sequel_core/dataset/pagination.rb +58 -43
- data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sequelizer.rb +41 -373
- data/lib/sequel_core/dataset/sql.rb +741 -632
- data/lib/sequel_core/dataset.rb +183 -168
- data/lib/sequel_core/deprecated.rb +1 -169
- data/lib/sequel_core/exceptions.rb +24 -19
- data/lib/sequel_core/migration.rb +44 -52
- data/lib/sequel_core/object_graph.rb +43 -42
- data/lib/sequel_core/pretty_table.rb +71 -76
- data/lib/sequel_core/schema/generator.rb +163 -105
- data/lib/sequel_core/schema/sql.rb +250 -93
- data/lib/sequel_core/schema.rb +2 -8
- data/lib/sequel_core/sql.rb +394 -0
- data/lib/sequel_core/worker.rb +37 -27
- data/lib/sequel_core.rb +99 -45
- data/spec/adapters/informix_spec.rb +0 -1
- data/spec/adapters/mysql_spec.rb +177 -124
- data/spec/adapters/oracle_spec.rb +0 -1
- data/spec/adapters/postgres_spec.rb +98 -58
- data/spec/adapters/sqlite_spec.rb +45 -4
- data/spec/blockless_filters_spec.rb +269 -0
- data/spec/connection_pool_spec.rb +21 -18
- data/spec/core_ext_spec.rb +169 -19
- data/spec/core_sql_spec.rb +56 -49
- data/spec/database_spec.rb +78 -17
- data/spec/dataset_spec.rb +300 -428
- data/spec/migration_spec.rb +1 -1
- data/spec/object_graph_spec.rb +5 -11
- data/spec/rcov.opts +1 -1
- data/spec/schema_generator_spec.rb +16 -4
- data/spec/schema_spec.rb +89 -10
- data/spec/sequelizer_spec.rb +56 -56
- data/spec/spec.opts +0 -5
- data/spec/spec_config.rb +7 -0
- data/spec/spec_config.rb.example +5 -5
- data/spec/spec_helper.rb +6 -0
- data/spec/worker_spec.rb +1 -1
- metadata +78 -63
data/lib/sequel_core/database.rb
CHANGED
@@ -1,14 +1,45 @@
|
|
1
|
-
require '
|
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
|
-
|
11
|
-
|
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
|
-
|
51
|
+
@quote_identifiers = opts[:quote_identifiers] || @@quote_identifiers
|
21
52
|
@single_threaded = opts[:single_threaded] || @@single_threaded
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
@
|
57
|
+
@loggers = Array(opts[:logger]) + Array(opts[:loggers])
|
31
58
|
::Sequel::DATABASES.push(self)
|
32
59
|
end
|
33
60
|
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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')
|
224
|
+
# DB.fetch('SELECT * FROM items'){|r| p r}
|
83
225
|
#
|
84
|
-
#
|
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
|
-
|
101
|
-
|
102
|
-
|
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
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
#
|
204
|
-
#
|
205
|
-
|
206
|
-
|
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
|
-
#
|
213
|
-
|
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
|
-
#
|
234
|
-
|
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
|
-
#
|
243
|
-
|
244
|
-
|
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
|
-
#
|
250
|
-
|
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
|
-
#
|
258
|
-
|
259
|
-
|
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
|
-
#
|
265
|
-
|
266
|
-
|
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
|
-
#
|
272
|
-
|
273
|
-
|
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
|
-
#
|
280
|
-
|
281
|
-
|
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
|
319
|
+
|
320
|
+
# Returns true if a table with the given name exists.
|
288
321
|
def table_exists?(name)
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
-
#
|
318
|
-
#
|
319
|
-
|
320
|
-
|
321
|
-
|
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.
|
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
|
-
|
342
|
-
|
343
|
-
|
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
|
-
#
|
354
|
-
#
|
355
|
-
def
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
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
|
-
|
410
|
-
|
411
|
-
|
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
|
-
|
444
|
-
c = adapter_class(opts[:adapter] || opts['adapter'])
|
413
|
+
value
|
445
414
|
end
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
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
|
-
|
456
|
-
|
457
|
-
#
|
458
|
-
def
|
459
|
-
|
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
|