sequel 3.11.0 → 3.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|