sequel 3.11.0 → 3.12.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 +70 -0
- data/Rakefile +1 -1
- data/doc/active_record.rdoc +896 -0
- data/doc/advanced_associations.rdoc +46 -31
- data/doc/association_basics.rdoc +14 -9
- data/doc/dataset_basics.rdoc +3 -3
- data/doc/migration.rdoc +1011 -0
- data/doc/model_hooks.rdoc +198 -0
- data/doc/querying.rdoc +811 -86
- data/doc/release_notes/3.12.0.txt +304 -0
- data/doc/sharding.rdoc +17 -0
- data/doc/sql.rdoc +537 -0
- data/doc/validations.rdoc +501 -0
- data/lib/sequel/adapters/jdbc.rb +19 -27
- data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
- data/lib/sequel/adapters/mysql.rb +5 -4
- data/lib/sequel/adapters/odbc.rb +3 -2
- data/lib/sequel/adapters/shared/mssql.rb +7 -6
- data/lib/sequel/adapters/shared/mysql.rb +2 -7
- data/lib/sequel/adapters/shared/postgres.rb +2 -8
- data/lib/sequel/adapters/shared/sqlite.rb +2 -5
- data/lib/sequel/adapters/sqlite.rb +4 -4
- data/lib/sequel/core.rb +0 -1
- data/lib/sequel/database.rb +2 -1060
- data/lib/sequel/database/connecting.rb +227 -0
- data/lib/sequel/database/dataset.rb +58 -0
- data/lib/sequel/database/dataset_defaults.rb +127 -0
- data/lib/sequel/database/logging.rb +62 -0
- data/lib/sequel/database/misc.rb +246 -0
- data/lib/sequel/database/query.rb +390 -0
- data/lib/sequel/database/schema_generator.rb +7 -3
- data/lib/sequel/database/schema_methods.rb +351 -7
- data/lib/sequel/dataset/actions.rb +9 -2
- data/lib/sequel/dataset/misc.rb +6 -2
- data/lib/sequel/dataset/mutation.rb +3 -11
- data/lib/sequel/dataset/query.rb +49 -6
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/migration.rb +395 -113
- data/lib/sequel/extensions/schema_dumper.rb +21 -13
- data/lib/sequel/model.rb +27 -25
- data/lib/sequel/model/associations.rb +72 -34
- data/lib/sequel/model/base.rb +74 -18
- data/lib/sequel/model/errors.rb +8 -1
- data/lib/sequel/plugins/active_model.rb +8 -0
- data/lib/sequel/plugins/association_pks.rb +87 -0
- data/lib/sequel/plugins/association_proxies.rb +8 -0
- data/lib/sequel/plugins/boolean_readers.rb +12 -6
- data/lib/sequel/plugins/caching.rb +14 -7
- data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
- data/lib/sequel/plugins/composition.rb +2 -1
- data/lib/sequel/plugins/force_encoding.rb +10 -7
- data/lib/sequel/plugins/hook_class_methods.rb +12 -11
- data/lib/sequel/plugins/identity_map.rb +9 -0
- data/lib/sequel/plugins/instance_hooks.rb +23 -13
- data/lib/sequel/plugins/lazy_attributes.rb +4 -1
- data/lib/sequel/plugins/many_through_many.rb +18 -4
- data/lib/sequel/plugins/nested_attributes.rb +1 -0
- data/lib/sequel/plugins/optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +9 -8
- data/lib/sequel/plugins/schema.rb +8 -0
- data/lib/sequel/plugins/serialization.rb +1 -3
- data/lib/sequel/plugins/sharding.rb +135 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
- data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
- data/lib/sequel/plugins/string_stripper.rb +26 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
- data/lib/sequel/plugins/timestamps.rb +15 -2
- data/lib/sequel/plugins/touch.rb +13 -0
- data/lib/sequel/plugins/update_primary_key.rb +48 -0
- data/lib/sequel/plugins/validation_class_methods.rb +8 -0
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/sql.rb +17 -20
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +5 -5
- data/spec/core/core_sql_spec.rb +17 -1
- data/spec/core/database_spec.rb +17 -5
- data/spec/core/dataset_spec.rb +31 -8
- data/spec/core/schema_generator_spec.rb +8 -1
- data/spec/core/schema_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +85 -0
- data/spec/extensions/hook_class_methods_spec.rb +9 -9
- data/spec/extensions/migration_spec.rb +339 -219
- data/spec/extensions/schema_dumper_spec.rb +28 -17
- data/spec/extensions/sharding_spec.rb +272 -0
- data/spec/extensions/single_table_inheritance_spec.rb +92 -4
- data/spec/extensions/skip_create_refresh_spec.rb +17 -0
- data/spec/extensions/string_stripper_spec.rb +23 -0
- data/spec/extensions/update_primary_key_spec.rb +65 -0
- data/spec/extensions/validation_class_methods_spec.rb +5 -5
- data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
- data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
- data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
- data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
- data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
- data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
- data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
- data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
- data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
- data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
- data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
- data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
- data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
- data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
- data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
- data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
- data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
- data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
- data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
- data/spec/integration/eager_loader_test.rb +20 -20
- data/spec/integration/migrator_test.rb +187 -0
- data/spec/integration/plugin_test.rb +150 -0
- data/spec/integration/schema_test.rb +13 -2
- data/spec/model/associations_spec.rb +41 -14
- data/spec/model/base_spec.rb +69 -0
- data/spec/model/eager_loading_spec.rb +7 -3
- data/spec/model/record_spec.rb +79 -4
- data/spec/model/validations_spec.rb +21 -9
- metadata +66 -5
- data/doc/schema.rdoc +0 -36
- data/lib/sequel/database/schema_sql.rb +0 -320
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Database
|
|
3
|
+
# ---------------------
|
|
4
|
+
# :section: Miscellaneous methods
|
|
5
|
+
# These methods don't fit neatly into another category.
|
|
6
|
+
# ---------------------
|
|
7
|
+
|
|
8
|
+
# Converts a uri to an options hash. These options are then passed
|
|
9
|
+
# to a newly created database object.
|
|
10
|
+
def self.uri_to_options(uri) # :nodoc:
|
|
11
|
+
{ :user => uri.user,
|
|
12
|
+
:password => uri.password,
|
|
13
|
+
:host => uri.host,
|
|
14
|
+
:port => uri.port,
|
|
15
|
+
:database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
|
|
16
|
+
end
|
|
17
|
+
private_class_method :uri_to_options
|
|
18
|
+
|
|
19
|
+
# The options for this database
|
|
20
|
+
attr_reader :opts
|
|
21
|
+
|
|
22
|
+
# Constructs a new instance of a database connection with the specified
|
|
23
|
+
# options hash.
|
|
24
|
+
#
|
|
25
|
+
# Sequel::Database is an abstract class that is not useful by itself.
|
|
26
|
+
#
|
|
27
|
+
# Takes the following options:
|
|
28
|
+
# * :default_schema : The default schema to use, should generally be nil
|
|
29
|
+
# * :disconnection_proc: A proc used to disconnect the connection.
|
|
30
|
+
# * :identifier_input_method: A string method symbol to call on identifiers going into the database
|
|
31
|
+
# * :identifier_output_method: A string method symbol to call on identifiers coming from the database
|
|
32
|
+
# * :loggers : An array of loggers to use.
|
|
33
|
+
# * :quote_identifiers : Whether to quote identifiers
|
|
34
|
+
# * :single_threaded : Whether to use a single-threaded connection pool
|
|
35
|
+
#
|
|
36
|
+
# All options given are also passed to the ConnectionPool. If a block
|
|
37
|
+
# is given, it is used as the connection_proc for the ConnectionPool.
|
|
38
|
+
def initialize(opts = {}, &block)
|
|
39
|
+
@opts ||= opts
|
|
40
|
+
@opts = connection_pool_default_options.merge(@opts)
|
|
41
|
+
@loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
|
|
42
|
+
self.log_warn_duration = @opts[:log_warn_duration]
|
|
43
|
+
@opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
|
|
44
|
+
block ||= proc{|server| connect(server)}
|
|
45
|
+
@opts[:servers] = {} if @opts[:servers].is_a?(String)
|
|
46
|
+
|
|
47
|
+
@opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
|
|
48
|
+
@schemas = {}
|
|
49
|
+
@default_schema = @opts.fetch(:default_schema, default_schema_default)
|
|
50
|
+
@prepared_statements = {}
|
|
51
|
+
@transactions = []
|
|
52
|
+
@identifier_input_method = nil
|
|
53
|
+
@identifier_output_method = nil
|
|
54
|
+
@quote_identifiers = nil
|
|
55
|
+
@pool = ConnectionPool.get_pool(@opts, &block)
|
|
56
|
+
|
|
57
|
+
::Sequel::DATABASES.push(self)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Cast the given type to a literal type
|
|
61
|
+
def cast_type_literal(type)
|
|
62
|
+
type_literal(:type=>type)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns a string representation of the database object including the
|
|
66
|
+
# class name and the connection URI (or the opts if the URI
|
|
67
|
+
# cannot be constructed).
|
|
68
|
+
def inspect
|
|
69
|
+
"#<#{self.class}: #{(uri rescue opts).inspect}>"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Proxy the literal call to the dataset, used for default values.
|
|
73
|
+
def literal(v)
|
|
74
|
+
schema_utility_dataset.literal(v)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Default serial primary key options.
|
|
78
|
+
def serial_primary_key_options
|
|
79
|
+
{:primary_key => true, :type => Integer, :auto_increment => true}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Whether the database and adapter support savepoints, false by default
|
|
83
|
+
def supports_savepoints?
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Typecast the value to the given column_type. Calls
|
|
88
|
+
# typecast_value_#{column_type} if the method exists,
|
|
89
|
+
# otherwise returns the value.
|
|
90
|
+
# This method should raise Sequel::InvalidValue if assigned value
|
|
91
|
+
# is invalid.
|
|
92
|
+
def typecast_value(column_type, value)
|
|
93
|
+
return nil if value.nil?
|
|
94
|
+
meth = "typecast_value_#{column_type}"
|
|
95
|
+
begin
|
|
96
|
+
respond_to?(meth, true) ? send(meth, value) : value
|
|
97
|
+
rescue ArgumentError, TypeError => e
|
|
98
|
+
raise Sequel.convert_exception_class(e, InvalidValue)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns the URI identifying the database.
|
|
103
|
+
# This method can raise an error if the database used options
|
|
104
|
+
# instead of a connection string.
|
|
105
|
+
def uri
|
|
106
|
+
uri = URI::Generic.new(
|
|
107
|
+
self.class.adapter_scheme.to_s,
|
|
108
|
+
nil,
|
|
109
|
+
@opts[:host],
|
|
110
|
+
@opts[:port],
|
|
111
|
+
nil,
|
|
112
|
+
"/#{@opts[:database]}",
|
|
113
|
+
nil,
|
|
114
|
+
nil,
|
|
115
|
+
nil
|
|
116
|
+
)
|
|
117
|
+
uri.user = @opts[:user]
|
|
118
|
+
uri.password = @opts[:password] if uri.user
|
|
119
|
+
uri.to_s
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Explicit alias of uri for easier subclassing.
|
|
123
|
+
def url
|
|
124
|
+
uri
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# Returns true when the object is considered blank.
|
|
130
|
+
# The only objects that are blank are nil, false,
|
|
131
|
+
# strings with all whitespace, and ones that respond
|
|
132
|
+
# true to empty?
|
|
133
|
+
def blank_object?(obj)
|
|
134
|
+
return obj.blank? if obj.respond_to?(:blank?)
|
|
135
|
+
case obj
|
|
136
|
+
when NilClass, FalseClass
|
|
137
|
+
true
|
|
138
|
+
when Numeric, TrueClass
|
|
139
|
+
false
|
|
140
|
+
when String
|
|
141
|
+
obj.strip.empty?
|
|
142
|
+
else
|
|
143
|
+
obj.respond_to?(:empty?) ? obj.empty? : false
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Which transaction errors to translate, blank by default.
|
|
148
|
+
def database_error_classes
|
|
149
|
+
[]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Convert the given exception to a DatabaseError, keeping message
|
|
153
|
+
# and traceback.
|
|
154
|
+
def raise_error(exception, opts={})
|
|
155
|
+
if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
|
|
156
|
+
raise Sequel.convert_exception_class(exception, opts[:disconnect] ? DatabaseDisconnectError : DatabaseError)
|
|
157
|
+
else
|
|
158
|
+
raise exception
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Typecast the value to an SQL::Blob
|
|
163
|
+
def typecast_value_blob(value)
|
|
164
|
+
value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Typecast the value to true, false, or nil
|
|
168
|
+
def typecast_value_boolean(value)
|
|
169
|
+
case value
|
|
170
|
+
when false, 0, "0", /\Af(alse)?\z/i
|
|
171
|
+
false
|
|
172
|
+
else
|
|
173
|
+
blank_object?(value) ? nil : true
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Typecast the value to a Date
|
|
178
|
+
def typecast_value_date(value)
|
|
179
|
+
case value
|
|
180
|
+
when Date
|
|
181
|
+
value
|
|
182
|
+
when DateTime, Time
|
|
183
|
+
Date.new(value.year, value.month, value.day)
|
|
184
|
+
when String
|
|
185
|
+
Sequel.string_to_date(value)
|
|
186
|
+
when Hash
|
|
187
|
+
Date.new(*[:year, :month, :day].map{|x| (value[x] || value[x.to_s]).to_i})
|
|
188
|
+
else
|
|
189
|
+
raise InvalidValue, "invalid value for Date: #{value.inspect}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Typecast the value to a DateTime or Time depending on Sequel.datetime_class
|
|
194
|
+
def typecast_value_datetime(value)
|
|
195
|
+
raise(Sequel::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless [DateTime, Date, Time, String, Hash].any?{|c| value.is_a?(c)}
|
|
196
|
+
klass = Sequel.datetime_class
|
|
197
|
+
if value.is_a?(Hash)
|
|
198
|
+
klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
|
199
|
+
else
|
|
200
|
+
Sequel.typecast_to_application_timestamp(value)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Typecast the value to a BigDecimal
|
|
205
|
+
def typecast_value_decimal(value)
|
|
206
|
+
case value
|
|
207
|
+
when BigDecimal
|
|
208
|
+
value
|
|
209
|
+
when String, Numeric
|
|
210
|
+
BigDecimal.new(value.to_s)
|
|
211
|
+
else
|
|
212
|
+
raise InvalidValue, "invalid value for BigDecimal: #{value.inspect}"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Typecast the value to a Float
|
|
217
|
+
def typecast_value_float(value)
|
|
218
|
+
Float(value)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Typecast the value to an Integer
|
|
222
|
+
def typecast_value_integer(value)
|
|
223
|
+
Integer(value)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Typecast the value to a String
|
|
227
|
+
def typecast_value_string(value)
|
|
228
|
+
value.to_s
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Typecast the value to a Time
|
|
232
|
+
def typecast_value_time(value)
|
|
233
|
+
case value
|
|
234
|
+
when Time
|
|
235
|
+
value
|
|
236
|
+
when String
|
|
237
|
+
Sequel.string_to_time(value)
|
|
238
|
+
when Hash
|
|
239
|
+
t = Time.now
|
|
240
|
+
Time.mktime(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
|
|
241
|
+
else
|
|
242
|
+
raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Database
|
|
3
|
+
# ---------------------
|
|
4
|
+
# :section: Methods that execute queries and/or return results
|
|
5
|
+
# This methods generally execute SQL code on the database server.
|
|
6
|
+
# ---------------------
|
|
7
|
+
|
|
8
|
+
SQL_BEGIN = 'BEGIN'.freeze
|
|
9
|
+
SQL_COMMIT = 'COMMIT'.freeze
|
|
10
|
+
SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
|
|
11
|
+
SQL_ROLLBACK = 'ROLLBACK'.freeze
|
|
12
|
+
SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TO SAVEPOINT autopoint_%d'.freeze
|
|
13
|
+
SQL_SAVEPOINT = 'SAVEPOINT autopoint_%d'.freeze
|
|
14
|
+
|
|
15
|
+
TRANSACTION_BEGIN = 'Transaction.begin'.freeze
|
|
16
|
+
TRANSACTION_COMMIT = 'Transaction.commit'.freeze
|
|
17
|
+
TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
|
|
18
|
+
|
|
19
|
+
POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
|
|
20
|
+
MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
|
|
21
|
+
MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
|
|
22
|
+
STRING_DEFAULT_RE = /\A'(.*)'\z/
|
|
23
|
+
|
|
24
|
+
# The prepared statement objects for this database, keyed by name
|
|
25
|
+
attr_reader :prepared_statements
|
|
26
|
+
|
|
27
|
+
# Runs the supplied SQL statement string on the database server.
|
|
28
|
+
# Alias for run.
|
|
29
|
+
def <<(sql)
|
|
30
|
+
run(sql)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Call the prepared statement with the given name with the given hash
|
|
34
|
+
# of arguments.
|
|
35
|
+
def call(ps_name, hash={})
|
|
36
|
+
prepared_statements[ps_name].call(hash)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Executes the given SQL on the database. This method should be overridden in descendants.
|
|
40
|
+
# This method should not be called directly by user code.
|
|
41
|
+
def execute(sql, opts={})
|
|
42
|
+
raise NotImplemented, "#execute should be overridden by adapters"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Method that should be used when submitting any DDL (Data Definition
|
|
46
|
+
# Language) SQL. By default, calls execute_dui.
|
|
47
|
+
# This method should not be called directly by user code.
|
|
48
|
+
def execute_ddl(sql, opts={}, &block)
|
|
49
|
+
execute_dui(sql, opts, &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Method that should be used when issuing a DELETE, UPDATE, or INSERT
|
|
53
|
+
# statement. By default, calls execute.
|
|
54
|
+
# This method should not be called directly by user code.
|
|
55
|
+
def execute_dui(sql, opts={}, &block)
|
|
56
|
+
execute(sql, opts, &block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Method that should be used when issuing a INSERT
|
|
60
|
+
# statement. By default, calls execute_dui.
|
|
61
|
+
# This method should not be called directly by user code.
|
|
62
|
+
def execute_insert(sql, opts={}, &block)
|
|
63
|
+
execute_dui(sql, opts, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns a single value from the database, e.g.:
|
|
67
|
+
#
|
|
68
|
+
# # SELECT 1
|
|
69
|
+
# DB.get(1) #=> 1
|
|
70
|
+
#
|
|
71
|
+
# # SELECT version()
|
|
72
|
+
# DB.get(:version.sql_function) #=> ...
|
|
73
|
+
def get(*args, &block)
|
|
74
|
+
dataset.get(*args, &block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Return a hash containing index information. Hash keys are index name symbols.
|
|
78
|
+
# Values are subhashes with two keys, :columns and :unique. The value of :columns
|
|
79
|
+
# is an array of symbols of column names. The value of :unique is true or false
|
|
80
|
+
# depending on if the index is unique.
|
|
81
|
+
#
|
|
82
|
+
# Should not include the primary key index, functional indexes, or partial indexes.
|
|
83
|
+
def indexes(table, opts={})
|
|
84
|
+
raise NotImplemented, "#indexes should be overridden by adapters"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Runs the supplied SQL statement string on the database server. Returns nil.
|
|
88
|
+
# Options:
|
|
89
|
+
# * :server - The server to run the SQL on.
|
|
90
|
+
def run(sql, opts={})
|
|
91
|
+
execute_ddl(sql, opts)
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Parse the schema from the database.
|
|
96
|
+
# Returns the schema for the given table as an array with all members being arrays of length 2,
|
|
97
|
+
# the first member being the column name, and the second member being a hash of column information.
|
|
98
|
+
# Available options are:
|
|
99
|
+
#
|
|
100
|
+
# * :reload - Get fresh information from the database, instead of using
|
|
101
|
+
# cached information. If table_name is blank, :reload should be used
|
|
102
|
+
# unless you are sure that schema has not been called before with a
|
|
103
|
+
# table_name, otherwise you may only getting the schemas for tables
|
|
104
|
+
# that have been requested explicitly.
|
|
105
|
+
# * :schema - An explicit schema to use. It may also be implicitly provided
|
|
106
|
+
# via the table name.
|
|
107
|
+
def schema(table, opts={})
|
|
108
|
+
raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
|
|
109
|
+
|
|
110
|
+
sch, table_name = schema_and_table(table)
|
|
111
|
+
quoted_name = quote_schema_table(table)
|
|
112
|
+
opts = opts.merge(:schema=>sch) if sch && !opts.include?(:schema)
|
|
113
|
+
|
|
114
|
+
@schemas.delete(quoted_name) if opts[:reload]
|
|
115
|
+
return @schemas[quoted_name] if @schemas[quoted_name]
|
|
116
|
+
|
|
117
|
+
cols = schema_parse_table(table_name, opts)
|
|
118
|
+
raise(Error, 'schema parsing returned no columns, table probably doesn\'t exist') if cols.nil? || cols.empty?
|
|
119
|
+
cols.each{|_,c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
|
|
120
|
+
@schemas[quoted_name] = cols
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns true if a table with the given name exists. This requires a query
|
|
124
|
+
# to the database.
|
|
125
|
+
def table_exists?(name)
|
|
126
|
+
begin
|
|
127
|
+
from(name).first
|
|
128
|
+
true
|
|
129
|
+
rescue
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Return all tables in the database as an array of symbols.
|
|
135
|
+
def tables(opts={})
|
|
136
|
+
raise NotImplemented, "#tables should be overridden by adapters"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Starts a database transaction. When a database transaction is used,
|
|
140
|
+
# either all statements are successful or none of the statements are
|
|
141
|
+
# successful. Note that MySQL MyISAM tabels do not support transactions.
|
|
142
|
+
#
|
|
143
|
+
# The following options are respected:
|
|
144
|
+
#
|
|
145
|
+
# * :server - The server to use for the transaction
|
|
146
|
+
# * :savepoint - Whether to create a new savepoint for this transaction,
|
|
147
|
+
# only respected if the database adapter supports savepoints. By
|
|
148
|
+
# default Sequel will reuse an existing transaction, so if you want to
|
|
149
|
+
# use a savepoint you must use this option.
|
|
150
|
+
def transaction(opts={}, &block)
|
|
151
|
+
synchronize(opts[:server]) do |conn|
|
|
152
|
+
return yield(conn) if already_in_transaction?(conn, opts)
|
|
153
|
+
_transaction(conn, &block)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
# Internal generic transaction method. Any exception raised by the given
|
|
160
|
+
# block will cause the transaction to be rolled back. If the exception is
|
|
161
|
+
# not Sequel::Rollback, the error will be reraised. If no exception occurs
|
|
162
|
+
# inside the block, the transaction is commited.
|
|
163
|
+
def _transaction(conn)
|
|
164
|
+
begin
|
|
165
|
+
add_transaction
|
|
166
|
+
t = begin_transaction(conn)
|
|
167
|
+
yield(conn)
|
|
168
|
+
rescue Exception => e
|
|
169
|
+
rollback_transaction(t) if t
|
|
170
|
+
transaction_error(e)
|
|
171
|
+
ensure
|
|
172
|
+
begin
|
|
173
|
+
commit_transaction(t) unless e
|
|
174
|
+
rescue Exception => e
|
|
175
|
+
raise_error(e, :classes=>database_error_classes)
|
|
176
|
+
ensure
|
|
177
|
+
remove_transaction(t)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Add the current thread to the list of active transactions
|
|
183
|
+
def add_transaction
|
|
184
|
+
th = Thread.current
|
|
185
|
+
if supports_savepoints?
|
|
186
|
+
unless @transactions.include?(th)
|
|
187
|
+
th[:sequel_transaction_depth] = 0
|
|
188
|
+
@transactions << th
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
@transactions << th
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Whether the current thread/connection is already inside a transaction
|
|
196
|
+
def already_in_transaction?(conn, opts)
|
|
197
|
+
@transactions.include?(Thread.current) && (!supports_savepoints? || !opts[:savepoint])
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# SQL to start a new savepoint
|
|
201
|
+
def begin_savepoint_sql(depth)
|
|
202
|
+
SQL_SAVEPOINT % depth
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Start a new database transaction on the given connection.
|
|
206
|
+
def begin_transaction(conn)
|
|
207
|
+
if supports_savepoints?
|
|
208
|
+
th = Thread.current
|
|
209
|
+
depth = th[:sequel_transaction_depth]
|
|
210
|
+
log_connection_execute(conn, depth > 0 ? begin_savepoint_sql(depth) : begin_transaction_sql)
|
|
211
|
+
th[:sequel_transaction_depth] += 1
|
|
212
|
+
else
|
|
213
|
+
log_connection_execute(conn, begin_transaction_sql)
|
|
214
|
+
end
|
|
215
|
+
conn
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# SQL to BEGIN a transaction.
|
|
219
|
+
def begin_transaction_sql
|
|
220
|
+
SQL_BEGIN
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Convert the given default, which should be a database specific string, into
|
|
224
|
+
# a ruby object.
|
|
225
|
+
def column_schema_to_ruby_default(default, type)
|
|
226
|
+
return if default.nil?
|
|
227
|
+
orig_default = default
|
|
228
|
+
if database_type == :postgres and m = POSTGRES_DEFAULT_RE.match(default)
|
|
229
|
+
default = m[1] || m[2]
|
|
230
|
+
end
|
|
231
|
+
if database_type == :mssql and m = MSSQL_DEFAULT_RE.match(default)
|
|
232
|
+
default = m[1] || m[2]
|
|
233
|
+
end
|
|
234
|
+
if [:string, :blob, :date, :datetime, :time, :enum].include?(type)
|
|
235
|
+
if database_type == :mysql
|
|
236
|
+
return if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
|
|
237
|
+
orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
|
|
238
|
+
end
|
|
239
|
+
return unless m = STRING_DEFAULT_RE.match(default)
|
|
240
|
+
default = m[1].gsub("''", "'")
|
|
241
|
+
end
|
|
242
|
+
res = begin
|
|
243
|
+
case type
|
|
244
|
+
when :boolean
|
|
245
|
+
case default
|
|
246
|
+
when /[f0]/i
|
|
247
|
+
false
|
|
248
|
+
when /[t1]/i
|
|
249
|
+
true
|
|
250
|
+
end
|
|
251
|
+
when :string, :enum
|
|
252
|
+
default
|
|
253
|
+
when :blob
|
|
254
|
+
Sequel::SQL::Blob.new(default)
|
|
255
|
+
when :integer
|
|
256
|
+
Integer(default)
|
|
257
|
+
when :float
|
|
258
|
+
Float(default)
|
|
259
|
+
when :date
|
|
260
|
+
Sequel.string_to_date(default)
|
|
261
|
+
when :datetime
|
|
262
|
+
DateTime.parse(default)
|
|
263
|
+
when :time
|
|
264
|
+
Sequel.string_to_time(default)
|
|
265
|
+
when :decimal
|
|
266
|
+
BigDecimal.new(default)
|
|
267
|
+
end
|
|
268
|
+
rescue
|
|
269
|
+
nil
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# SQL to commit a savepoint
|
|
274
|
+
def commit_savepoint_sql(depth)
|
|
275
|
+
SQL_RELEASE_SAVEPOINT % depth
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Commit the active transaction on the connection
|
|
279
|
+
def commit_transaction(conn)
|
|
280
|
+
if supports_savepoints?
|
|
281
|
+
depth = Thread.current[:sequel_transaction_depth]
|
|
282
|
+
log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
|
|
283
|
+
else
|
|
284
|
+
log_connection_execute(conn, commit_transaction_sql)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# SQL to COMMIT a transaction.
|
|
289
|
+
def commit_transaction_sql
|
|
290
|
+
SQL_COMMIT
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Method called on the connection object to execute SQL on the database,
|
|
294
|
+
# used by the transaction code.
|
|
295
|
+
def connection_execute_method
|
|
296
|
+
:execute
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Return a Method object for the dataset's output_identifier_method.
|
|
300
|
+
# Used in metadata parsing to make sure the returned information is in the
|
|
301
|
+
# correct format.
|
|
302
|
+
def input_identifier_meth
|
|
303
|
+
dataset.method(:input_identifier)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Return a dataset that uses the default identifier input and output methods
|
|
307
|
+
# for this database. Used when parsing metadata so that column symbols are
|
|
308
|
+
# returned as expected.
|
|
309
|
+
def metadata_dataset
|
|
310
|
+
return @metadata_dataset if @metadata_dataset
|
|
311
|
+
ds = dataset
|
|
312
|
+
ds.identifier_input_method = identifier_input_method_default
|
|
313
|
+
ds.identifier_output_method = identifier_output_method_default
|
|
314
|
+
@metadata_dataset = ds
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Return a Method object for the dataset's output_identifier_method.
|
|
318
|
+
# Used in metadata parsing to make sure the returned information is in the
|
|
319
|
+
# correct format.
|
|
320
|
+
def output_identifier_meth
|
|
321
|
+
dataset.method(:output_identifier)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# SQL to ROLLBACK a transaction.
|
|
325
|
+
def rollback_transaction_sql
|
|
326
|
+
SQL_ROLLBACK
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Remove the cached schema for the given schema name
|
|
330
|
+
def remove_cached_schema(table)
|
|
331
|
+
@schemas.delete(quote_schema_table(table)) if @schemas
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Remove the current thread from the list of active transactions
|
|
335
|
+
def remove_transaction(conn)
|
|
336
|
+
th = Thread.current
|
|
337
|
+
@transactions.delete(th) if !supports_savepoints? || ((th[:sequel_transaction_depth] -= 1) <= 0)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# SQL to rollback to a savepoint
|
|
341
|
+
def rollback_savepoint_sql(depth)
|
|
342
|
+
SQL_ROLLBACK_TO_SAVEPOINT % depth
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Rollback the active transaction on the connection
|
|
346
|
+
def rollback_transaction(conn)
|
|
347
|
+
if supports_savepoints?
|
|
348
|
+
depth = Thread.current[:sequel_transaction_depth]
|
|
349
|
+
log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
|
|
350
|
+
else
|
|
351
|
+
log_connection_execute(conn, rollback_transaction_sql)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Match the database's column type to a ruby type via a
|
|
356
|
+
# regular expression. The following ruby types are supported:
|
|
357
|
+
# integer, string, date, datetime, boolean, and float.
|
|
358
|
+
def schema_column_type(db_type)
|
|
359
|
+
case db_type
|
|
360
|
+
when /\Ainterval\z/io
|
|
361
|
+
:interval
|
|
362
|
+
when /\A(character( varying)?|n?(var)?char|n?text)/io
|
|
363
|
+
:string
|
|
364
|
+
when /\A(int(eger)?|(big|small|tiny)int)/io
|
|
365
|
+
:integer
|
|
366
|
+
when /\Adate\z/io
|
|
367
|
+
:date
|
|
368
|
+
when /\A((small)?datetime|timestamp( with(out)? time zone)?)\z/io
|
|
369
|
+
:datetime
|
|
370
|
+
when /\Atime( with(out)? time zone)?\z/io
|
|
371
|
+
:time
|
|
372
|
+
when /\A(boolean|bit)\z/io
|
|
373
|
+
:boolean
|
|
374
|
+
when /\A(real|float|double( precision)?)\z/io
|
|
375
|
+
:float
|
|
376
|
+
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)\z/io
|
|
377
|
+
$1 && $1 == '0' ? :integer : :decimal
|
|
378
|
+
when /bytea|blob|image|(var)?binary/io
|
|
379
|
+
:blob
|
|
380
|
+
when /\Aenum/io
|
|
381
|
+
:enum
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Raise a database error unless the exception is an Rollback.
|
|
386
|
+
def transaction_error(e)
|
|
387
|
+
raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|