sequel 3.21.0 → 3.29.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 +413 -3
- data/README.rdoc +20 -6
- data/Rakefile +23 -16
- data/bin/sequel +1 -5
- data/doc/association_basics.rdoc +210 -43
- data/doc/dataset_basics.rdoc +4 -4
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +15 -538
- data/doc/model_hooks.rdoc +72 -27
- data/doc/opening_databases.rdoc +86 -50
- data/doc/prepared_statements.rdoc +40 -13
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/release_notes/3.25.0.txt +88 -0
- data/doc/release_notes/3.26.0.txt +88 -0
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +115 -0
- data/doc/transactions.rdoc +137 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/ado.rb +40 -18
- data/lib/sequel/adapters/amalgalite.rb +15 -7
- data/lib/sequel/adapters/db2.rb +175 -86
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/do.rb +17 -36
- data/lib/sequel/adapters/firebird.rb +27 -208
- data/lib/sequel/adapters/ibmdb.rb +448 -0
- data/lib/sequel/adapters/informix.rb +6 -23
- data/lib/sequel/adapters/jdbc/as400.rb +5 -31
- data/lib/sequel/adapters/jdbc/db2.rb +44 -0
- data/lib/sequel/adapters/jdbc/derby.rb +217 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
- data/lib/sequel/adapters/jdbc/h2.rb +26 -17
- data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
- data/lib/sequel/adapters/jdbc/informix.rb +26 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
- data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
- data/lib/sequel/adapters/jdbc.rb +107 -38
- data/lib/sequel/adapters/mock.rb +315 -0
- data/lib/sequel/adapters/mysql.rb +78 -182
- data/lib/sequel/adapters/mysql2.rb +24 -23
- data/lib/sequel/adapters/odbc/db2.rb +17 -0
- data/lib/sequel/adapters/odbc/mssql.rb +0 -5
- data/lib/sequel/adapters/odbc.rb +28 -9
- data/lib/sequel/adapters/openbase.rb +2 -4
- data/lib/sequel/adapters/oracle.rb +349 -51
- data/lib/sequel/adapters/postgres.rb +169 -21
- data/lib/sequel/adapters/shared/access.rb +21 -6
- data/lib/sequel/adapters/shared/db2.rb +288 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +108 -65
- data/lib/sequel/adapters/shared/mysql.rb +56 -13
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
- data/lib/sequel/adapters/shared/oracle.rb +185 -34
- data/lib/sequel/adapters/shared/postgres.rb +107 -58
- data/lib/sequel/adapters/shared/progress.rb +0 -6
- data/lib/sequel/adapters/shared/sqlite.rb +168 -41
- data/lib/sequel/adapters/sqlite.rb +27 -18
- data/lib/sequel/adapters/swift/mysql.rb +9 -5
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +6 -4
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/tinytds.rb +152 -17
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/core.rb +46 -5
- data/lib/sequel/database/connecting.rb +5 -6
- data/lib/sequel/database/dataset.rb +4 -4
- data/lib/sequel/database/dataset_defaults.rb +59 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +100 -19
- data/lib/sequel/database/query.rb +153 -58
- data/lib/sequel/database/schema_generator.rb +8 -5
- data/lib/sequel/database/schema_methods.rb +59 -29
- data/lib/sequel/dataset/actions.rb +194 -57
- data/lib/sequel/dataset/features.rb +81 -8
- data/lib/sequel/dataset/graph.rb +8 -7
- data/lib/sequel/dataset/misc.rb +50 -23
- data/lib/sequel/dataset/mutation.rb +5 -6
- data/lib/sequel/dataset/prepared_statements.rb +32 -15
- data/lib/sequel/dataset/query.rb +223 -42
- data/lib/sequel/dataset/sql.rb +58 -61
- data/lib/sequel/dataset.rb +8 -0
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +6 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +997 -331
- data/lib/sequel/model/base.rb +386 -129
- data/lib/sequel/model/errors.rb +1 -1
- data/lib/sequel/model/exceptions.rb +5 -1
- data/lib/sequel/model.rb +15 -8
- data/lib/sequel/plugins/association_pks.rb +22 -4
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +114 -7
- data/lib/sequel/plugins/many_through_many.rb +65 -7
- data/lib/sequel/plugins/prepared_statements.rb +151 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +6 -1
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/sharding.rb +12 -25
- data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +2 -2
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +107 -51
- data/lib/sequel/timezones.rb +44 -35
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/mssql_spec.rb +65 -29
- data/spec/adapters/mysql_spec.rb +122 -123
- data/spec/adapters/oracle_spec.rb +48 -76
- data/spec/adapters/postgres_spec.rb +213 -61
- data/spec/adapters/spec_helper.rb +6 -5
- data/spec/adapters/sqlite_spec.rb +35 -21
- data/spec/core/connection_pool_spec.rb +71 -92
- data/spec/core/core_sql_spec.rb +20 -31
- data/spec/core/database_spec.rb +729 -508
- data/spec/core/dataset_spec.rb +980 -1044
- data/spec/core/expression_filters_spec.rb +159 -42
- data/spec/core/mock_adapter_spec.rb +378 -0
- data/spec/core/object_graph_spec.rb +48 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +298 -38
- data/spec/core/spec_helper.rb +6 -48
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/class_table_inheritance_spec.rb +1 -1
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/instance_hooks_spec.rb +71 -0
- data/spec/extensions/many_through_many_spec.rb +195 -20
- data/spec/extensions/migration_spec.rb +17 -17
- data/spec/extensions/named_timezones_spec.rb +22 -2
- data/spec/extensions/nested_attributes_spec.rb +4 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +13 -21
- data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
- data/spec/extensions/serialization_spec.rb +5 -8
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +7 -1
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/extensions/xml_serializer_spec.rb +16 -4
- data/spec/integration/associations_test.rb +522 -21
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +375 -62
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +304 -116
- data/spec/integration/prepared_statement_test.rb +200 -120
- data/spec/integration/schema_test.rb +161 -30
- data/spec/integration/spec_helper.rb +39 -30
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +172 -5
- data/spec/integration/type_test.rb +17 -3
- data/spec/model/association_reflection_spec.rb +174 -7
- data/spec/model/associations_spec.rb +849 -661
- data/spec/model/base_spec.rb +255 -95
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +640 -676
- data/spec/model/hooks_spec.rb +309 -67
- data/spec/model/model_spec.rb +207 -167
- data/spec/model/plugins_spec.rb +24 -13
- data/spec/model/record_spec.rb +321 -218
- data/spec/model/spec_helper.rb +13 -71
- data/spec/model/validations_spec.rb +11 -0
- metadata +95 -38
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Mock
|
|
3
|
+
# Connection class for Sequel's mock adapter.
|
|
4
|
+
class Connection
|
|
5
|
+
# Sequel::Mock::Database object that created this connection
|
|
6
|
+
attr_reader :db
|
|
7
|
+
|
|
8
|
+
# Shard this connection operates on, when using Sequel's
|
|
9
|
+
# sharding support (always :default for databases not using
|
|
10
|
+
# sharding).
|
|
11
|
+
attr_reader :server
|
|
12
|
+
|
|
13
|
+
# The specific database options for this connection.
|
|
14
|
+
attr_reader :opts
|
|
15
|
+
|
|
16
|
+
# Store the db, server, and opts.
|
|
17
|
+
def initialize(db, server, opts)
|
|
18
|
+
@db = db
|
|
19
|
+
@server = server
|
|
20
|
+
@opts = opts
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Delegate to the db's #_execute method.
|
|
24
|
+
def execute(sql)
|
|
25
|
+
@db.send(:_execute, self, sql)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Database class for Sequel's mock adapter.
|
|
30
|
+
class Database < Sequel::Database
|
|
31
|
+
set_adapter_scheme :mock
|
|
32
|
+
|
|
33
|
+
# Set the autogenerated primary key integer
|
|
34
|
+
# to be returned when running an insert query.
|
|
35
|
+
# Argument types supported:
|
|
36
|
+
#
|
|
37
|
+
# nil :: Return nil for all inserts
|
|
38
|
+
# Integer :: Starting integer for next insert, with
|
|
39
|
+
# futher inserts getting an incremented
|
|
40
|
+
# value
|
|
41
|
+
# Array :: First insert gets the first value in the
|
|
42
|
+
# array, second gets the second value, etc.
|
|
43
|
+
# Proc :: Called with the insert SQL query, uses
|
|
44
|
+
# the value returned
|
|
45
|
+
# Class :: Should be an Exception subclass, will create a new
|
|
46
|
+
# instance an raise it wrapped in a DatabaseError.
|
|
47
|
+
attr_writer :autoid
|
|
48
|
+
|
|
49
|
+
# Set the columns to set in the dataset when the dataset fetches
|
|
50
|
+
# rows. Argument types supported:
|
|
51
|
+
# nil :: Set no columns
|
|
52
|
+
# Array of Symbols: Used for all datasets
|
|
53
|
+
# Array (otherwise): First retrieval gets the first value in the
|
|
54
|
+
# array, second gets the second value, etc.
|
|
55
|
+
# Proc :: Called with the select SQL query, uses the value
|
|
56
|
+
# returned, which should be an array of symbols
|
|
57
|
+
attr_writer :columns
|
|
58
|
+
|
|
59
|
+
# Set the hashes to yield by execute when retrieving rows.
|
|
60
|
+
# Argument types supported:
|
|
61
|
+
#
|
|
62
|
+
# nil :: Yield no rows
|
|
63
|
+
# Hash :: Always yield a single row with this hash
|
|
64
|
+
# Array of Hashes :: Yield separately for each hash in this array
|
|
65
|
+
# Array (otherwise) :: First retrieval gets the first value
|
|
66
|
+
# in the array, second gets the second value, etc.
|
|
67
|
+
# Proc :: Called with the select SQL query, uses
|
|
68
|
+
# the value returned, which should be a hash or
|
|
69
|
+
# array of hashes.
|
|
70
|
+
# Class :: Should be an Exception subclass, will create a new
|
|
71
|
+
# instance an raise it wrapped in a DatabaseError.
|
|
72
|
+
attr_writer :fetch
|
|
73
|
+
|
|
74
|
+
# Set the number of rows to return from update or delete.
|
|
75
|
+
# Argument types supported:
|
|
76
|
+
#
|
|
77
|
+
# nil :: Return 0 for all updates and deletes
|
|
78
|
+
# Integer :: Used for all updates and deletes
|
|
79
|
+
# Array :: First update/delete gets the first value in the
|
|
80
|
+
# array, second gets the second value, etc.
|
|
81
|
+
# Proc :: Called with the update/delete SQL query, uses
|
|
82
|
+
# the value returned.
|
|
83
|
+
# Class :: Should be an Exception subclass, will create a new
|
|
84
|
+
# instance an raise it wrapped in a DatabaseError.
|
|
85
|
+
attr_writer :numrows
|
|
86
|
+
|
|
87
|
+
# Additional options supported:
|
|
88
|
+
#
|
|
89
|
+
# :autoid :: Call #autoid= with the value
|
|
90
|
+
# :columns :: Call #columns= with the value
|
|
91
|
+
# :fetch :: Call #fetch= with the value
|
|
92
|
+
# :numrows :: Call #numrows= with the value
|
|
93
|
+
# :extend :: A module the object is extended with.
|
|
94
|
+
# :sqls :: The array to store the SQL queries in.
|
|
95
|
+
def initialize(opts=nil)
|
|
96
|
+
super
|
|
97
|
+
self.autoid = opts[:autoid]
|
|
98
|
+
self.columns = opts[:columns]
|
|
99
|
+
self.fetch = opts[:fetch]
|
|
100
|
+
self.numrows = opts[:numrows]
|
|
101
|
+
extend(opts[:extend]) if opts[:extend]
|
|
102
|
+
@sqls = opts[:sqls] || []
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Return a related Connection option connecting to the given shard.
|
|
106
|
+
def connect(server)
|
|
107
|
+
Connection.new(self, server, server_opts(server))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Store the sql used for later retrieval with #sqls, and return
|
|
111
|
+
# the appropriate value using either the #autoid, #fetch, or
|
|
112
|
+
# #numrows methods.
|
|
113
|
+
def execute(sql, opts={}, &block)
|
|
114
|
+
synchronize(opts[:server]){|c| _execute(c, sql, opts, &block)}
|
|
115
|
+
end
|
|
116
|
+
alias execute_ddl execute
|
|
117
|
+
|
|
118
|
+
# Store the sql used, and return the value of the #numrows method.
|
|
119
|
+
def execute_dui(sql, opts={})
|
|
120
|
+
execute(sql, opts.merge(:meth=>:numrows))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Store the sql used, and return the value of the #autoid method.
|
|
124
|
+
def execute_insert(sql, opts={})
|
|
125
|
+
execute(sql, opts.merge(:meth=>:autoid))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Return all stored SQL queries, and clear the cache
|
|
129
|
+
# of SQL queries.
|
|
130
|
+
def sqls
|
|
131
|
+
s = @sqls.dup
|
|
132
|
+
@sqls.clear
|
|
133
|
+
s
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Enable use of savepoints.
|
|
137
|
+
def supports_savepoints?
|
|
138
|
+
true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def _autoid(sql, v, ds=nil)
|
|
144
|
+
case v
|
|
145
|
+
when Integer
|
|
146
|
+
if ds
|
|
147
|
+
ds.autoid += 1 if ds.autoid.is_a?(Integer)
|
|
148
|
+
else
|
|
149
|
+
@autoid += 1
|
|
150
|
+
end
|
|
151
|
+
v
|
|
152
|
+
else
|
|
153
|
+
_nextres(v, sql, nil)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def _execute(c, sql, opts={}, &block)
|
|
158
|
+
sql += " -- args: #{opts[:arguments].inspect}" if opts[:arguments]
|
|
159
|
+
sql += " -- #{@opts[:append]}" if @opts[:append]
|
|
160
|
+
sql += " -- #{c.server}" if c.server != :default
|
|
161
|
+
log_info(sql)
|
|
162
|
+
@sqls << sql
|
|
163
|
+
|
|
164
|
+
ds = opts[:dataset]
|
|
165
|
+
begin
|
|
166
|
+
if block
|
|
167
|
+
columns(ds, sql) if ds
|
|
168
|
+
_fetch(sql, ds._fetch || @fetch, &block)
|
|
169
|
+
elsif meth = opts[:meth]
|
|
170
|
+
if meth == :numrows
|
|
171
|
+
_numrows(sql, ds.numrows || @numrows)
|
|
172
|
+
else
|
|
173
|
+
v = ds.autoid
|
|
174
|
+
_autoid(sql, v || @autoid, (ds if v))
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
rescue => e
|
|
178
|
+
raise_error(e)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def _fetch(sql, f, &block)
|
|
183
|
+
case f
|
|
184
|
+
when Hash
|
|
185
|
+
yield f.dup
|
|
186
|
+
when Array
|
|
187
|
+
if f.all?{|h| h.is_a?(Hash)}
|
|
188
|
+
f.each{|h| yield h.dup}
|
|
189
|
+
else
|
|
190
|
+
_fetch(sql, f.shift, &block)
|
|
191
|
+
end
|
|
192
|
+
when Proc
|
|
193
|
+
h = f.call(sql)
|
|
194
|
+
if h.is_a?(Hash)
|
|
195
|
+
yield h.dup
|
|
196
|
+
elsif h
|
|
197
|
+
h.each{|h1| yield h1.dup}
|
|
198
|
+
end
|
|
199
|
+
when Class
|
|
200
|
+
if f < Exception
|
|
201
|
+
raise f
|
|
202
|
+
else
|
|
203
|
+
raise Error, "Invalid @autoid/@numrows attribute: #{v.inspect}"
|
|
204
|
+
end
|
|
205
|
+
when nil
|
|
206
|
+
# nothing
|
|
207
|
+
else
|
|
208
|
+
raise Error, "Invalid @fetch attribute: #{f.inspect}"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def _nextres(v, sql, default)
|
|
213
|
+
case v
|
|
214
|
+
when Integer
|
|
215
|
+
v
|
|
216
|
+
when Array
|
|
217
|
+
v.empty? ? default : _nextres(v.shift, sql, default)
|
|
218
|
+
when Proc
|
|
219
|
+
v.call(sql)
|
|
220
|
+
when Class
|
|
221
|
+
if v < Exception
|
|
222
|
+
raise v
|
|
223
|
+
else
|
|
224
|
+
raise Error, "Invalid @autoid/@numrows attribute: #{v.inspect}"
|
|
225
|
+
end
|
|
226
|
+
when nil
|
|
227
|
+
default
|
|
228
|
+
else
|
|
229
|
+
raise Error, "Invalid @autoid/@numrows attribute: #{v.inspect}"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def _numrows(sql, v)
|
|
234
|
+
_nextres(v, sql, 0)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def columns(ds, sql, cs=@columns)
|
|
238
|
+
case cs
|
|
239
|
+
when Array
|
|
240
|
+
unless cs.empty?
|
|
241
|
+
if cs.all?{|c| c.is_a?(Symbol)}
|
|
242
|
+
ds.columns(*cs)
|
|
243
|
+
else
|
|
244
|
+
columns(ds, sql, cs.shift)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
when Proc
|
|
248
|
+
ds.columns(*cs.call(sql))
|
|
249
|
+
when nil
|
|
250
|
+
# nothing
|
|
251
|
+
else
|
|
252
|
+
raise Error, "Invalid @columns attribute: #{cs.inspect}"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def disconnect_connection(c)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def quote_identifiers_default
|
|
260
|
+
false
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def identifier_input_method_default
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def identifier_input_method_default
|
|
268
|
+
nil
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
class Dataset < Sequel::Dataset
|
|
273
|
+
Database::DatasetClass = self
|
|
274
|
+
|
|
275
|
+
# Override the databases's autoid setting for this dataset
|
|
276
|
+
attr_accessor :autoid
|
|
277
|
+
|
|
278
|
+
# Override the databases's fetch setting for this dataset
|
|
279
|
+
attr_accessor :_fetch
|
|
280
|
+
|
|
281
|
+
# Override the databases's numrows setting for this dataset
|
|
282
|
+
attr_accessor :numrows
|
|
283
|
+
|
|
284
|
+
# If arguments are provided, use them to set the columns
|
|
285
|
+
# for this dataset and return self. Otherwise, use the
|
|
286
|
+
# default Sequel behavior and return the columns.
|
|
287
|
+
def columns(*cs)
|
|
288
|
+
if cs.empty?
|
|
289
|
+
super
|
|
290
|
+
else
|
|
291
|
+
@columns = cs
|
|
292
|
+
self
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def fetch_rows(sql, &block)
|
|
297
|
+
execute(sql, &block)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
def execute(sql, opts={}, &block)
|
|
303
|
+
super(sql, opts.merge(:dataset=>self), &block)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def execute_dui(sql, opts={}, &block)
|
|
307
|
+
super(sql, opts.merge(:dataset=>self), &block)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def execute_insert(sql, opts={}, &block)
|
|
311
|
+
super(sql, opts.merge(:dataset=>self), &block)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
@@ -5,97 +5,63 @@ rescue LoadError
|
|
|
5
5
|
end
|
|
6
6
|
raise(LoadError, "require 'mysql' did not define Mysql::CLIENT_MULTI_RESULTS!\n You are probably using the pure ruby mysql.rb driver,\n which Sequel does not support. You need to install\n the C based adapter, and make sure that the mysql.so\n file is loaded instead of the mysql.rb file.\n") unless defined?(Mysql::CLIENT_MULTI_RESULTS)
|
|
7
7
|
|
|
8
|
-
Sequel.require %w'shared/
|
|
8
|
+
Sequel.require %w'shared/mysql_prepared_statements', 'adapters'
|
|
9
9
|
|
|
10
10
|
module Sequel
|
|
11
11
|
# Module for holding all MySQL-related classes and modules for Sequel.
|
|
12
12
|
module MySQL
|
|
13
13
|
TYPE_TRANSLATOR = tt = Class.new do
|
|
14
14
|
def boolean(s) s.to_i != 0 end
|
|
15
|
-
def blob(s) ::Sequel::SQL::Blob.new(s) end
|
|
16
15
|
def integer(s) s.to_i end
|
|
17
16
|
def float(s) s.to_f end
|
|
18
|
-
def decimal(s) ::BigDecimal.new(s) end
|
|
19
|
-
def date(s) ::Sequel.string_to_date(s) end
|
|
20
|
-
def time(s) ::Sequel.string_to_time(s) end
|
|
21
|
-
def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
|
|
22
|
-
def date_conv(s) ::Sequel::MySQL.convert_date_time(:string_to_date, s) end
|
|
23
|
-
def time_conv(s) ::Sequel::MySQL.convert_date_time(:string_to_time, s) end
|
|
24
|
-
def timestamp_conv(s) ::Sequel::MySQL.convert_date_time(:database_to_application_timestamp, s) end
|
|
25
17
|
end.new
|
|
26
18
|
|
|
27
19
|
# Hash with integer keys and callable values for converting MySQL types.
|
|
28
20
|
MYSQL_TYPES = {}
|
|
29
21
|
{
|
|
30
|
-
[0, 246]
|
|
31
|
-
[2, 3, 8, 9, 13, 247, 248]
|
|
32
|
-
[4, 5]
|
|
33
|
-
[249, 250, 251, 252]
|
|
22
|
+
[0, 246] => ::BigDecimal.method(:new),
|
|
23
|
+
[2, 3, 8, 9, 13, 247, 248] => tt.method(:integer),
|
|
24
|
+
[4, 5] => tt.method(:float),
|
|
25
|
+
[249, 250, 251, 252] => ::Sequel::SQL::Blob.method(:new)
|
|
34
26
|
}.each do |k,v|
|
|
35
27
|
k.each{|n| MYSQL_TYPES[n] = v}
|
|
36
28
|
end
|
|
37
29
|
|
|
38
|
-
# Modify the type translator used for the tinyint type based
|
|
39
|
-
# on the value given.
|
|
40
|
-
def self.convert_tinyint_to_bool=(v)
|
|
41
|
-
MYSQL_TYPES[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
|
|
42
|
-
@convert_tinyint_to_bool = v
|
|
43
|
-
end
|
|
44
|
-
self.convert_tinyint_to_bool = convert_tinyint_to_bool
|
|
45
|
-
|
|
46
30
|
class << self
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
|
50
|
-
# it returns the strings as is.
|
|
51
|
-
attr_reader :convert_invalid_date_time
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Modify the type translators for the date, time, and timestamp types
|
|
55
|
-
# depending on the value given.
|
|
56
|
-
def self.convert_invalid_date_time=(v)
|
|
57
|
-
MYSQL_TYPES[11] = TYPE_TRANSLATOR.method(v == false ? :time : :time_conv)
|
|
58
|
-
m = TYPE_TRANSLATOR.method(v == false ? :date : :date_conv)
|
|
59
|
-
[10, 14].each{|i| MYSQL_TYPES[i] = m}
|
|
60
|
-
m = TYPE_TRANSLATOR.method(v == false ? :timestamp : :timestamp_conv)
|
|
61
|
-
[7, 12].each{|i| MYSQL_TYPES[i] = m}
|
|
62
|
-
@convert_invalid_date_time = v
|
|
31
|
+
# Whether to convert invalid date time values by default.
|
|
32
|
+
attr_accessor :convert_invalid_date_time
|
|
63
33
|
end
|
|
64
34
|
self.convert_invalid_date_time = false
|
|
65
35
|
|
|
66
|
-
# If convert_invalid_date_time is nil, :nil, or :string and
|
|
67
|
-
# the conversion raises an InvalidValue exception, return v
|
|
68
|
-
# if :string and nil otherwise.
|
|
69
|
-
def self.convert_date_time(meth, v)
|
|
70
|
-
begin
|
|
71
|
-
Sequel.send(meth, v)
|
|
72
|
-
rescue InvalidValue
|
|
73
|
-
case @convert_invalid_date_time
|
|
74
|
-
when nil, :nil
|
|
75
|
-
nil
|
|
76
|
-
when :string
|
|
77
|
-
v
|
|
78
|
-
else
|
|
79
|
-
raise
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
36
|
# Database class for MySQL databases used with Sequel.
|
|
85
37
|
class Database < Sequel::Database
|
|
86
38
|
include Sequel::MySQL::DatabaseMethods
|
|
39
|
+
include Sequel::MySQL::PreparedStatements::DatabaseMethods
|
|
87
40
|
|
|
88
41
|
# Mysql::Error messages that indicate the current connection should be disconnected
|
|
89
42
|
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|Lost connection to MySQL server during query)/
|
|
90
43
|
|
|
91
44
|
set_adapter_scheme :mysql
|
|
45
|
+
|
|
46
|
+
# Hash of conversion procs for the current database
|
|
47
|
+
attr_reader :conversion_procs
|
|
48
|
+
#
|
|
49
|
+
# Whether to convert tinyint columns to bool for the current database
|
|
50
|
+
attr_reader :convert_tinyint_to_bool
|
|
51
|
+
|
|
52
|
+
# By default, Sequel raises an exception if in invalid date or time is used.
|
|
53
|
+
# However, if this is set to nil or :nil, the adapter treats dates
|
|
54
|
+
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
|
55
|
+
# it returns the strings as is.
|
|
56
|
+
attr_reader :convert_invalid_date_time
|
|
92
57
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
58
|
+
def initialize(opts={})
|
|
59
|
+
super
|
|
60
|
+
@conversion_procs = MYSQL_TYPES.dup
|
|
61
|
+
self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
|
|
62
|
+
self.convert_invalid_date_time = Sequel::MySQL.convert_invalid_date_time
|
|
97
63
|
end
|
|
98
|
-
|
|
64
|
+
|
|
99
65
|
# Connect to the database. In addition to the usual database options,
|
|
100
66
|
# the following options have effect:
|
|
101
67
|
#
|
|
@@ -139,7 +105,7 @@ module Sequel
|
|
|
139
105
|
opts[:user],
|
|
140
106
|
opts[:password],
|
|
141
107
|
opts[:database],
|
|
142
|
-
opts[:port],
|
|
108
|
+
(opts[:port].to_i if opts[:port]),
|
|
143
109
|
opts[:socket],
|
|
144
110
|
Mysql::CLIENT_MULTI_RESULTS +
|
|
145
111
|
Mysql::CLIENT_MULTI_STATEMENTS +
|
|
@@ -161,30 +127,31 @@ module Sequel
|
|
|
161
127
|
|
|
162
128
|
sqls.each{|sql| log_yield(sql){conn.query(sql)}}
|
|
163
129
|
|
|
164
|
-
|
|
165
|
-
attr_accessor :prepared_statements
|
|
166
|
-
end
|
|
167
|
-
conn.prepared_statements = {}
|
|
130
|
+
add_prepared_statements_cache(conn)
|
|
168
131
|
conn
|
|
169
132
|
end
|
|
170
133
|
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
|
|
134
|
+
# Modify the type translators for the date, time, and timestamp types
|
|
135
|
+
# depending on the value given.
|
|
136
|
+
def convert_invalid_date_time=(v)
|
|
137
|
+
m0 = ::Sequel.method(:string_to_time)
|
|
138
|
+
@conversion_procs[11] = (v != false) ? lambda{|v| convert_date_time(v, &m0)} : m0
|
|
139
|
+
m1 = ::Sequel.method(:string_to_date)
|
|
140
|
+
m = (v != false) ? lambda{|v| convert_date_time(v, &m1)} : m1
|
|
141
|
+
[10, 14].each{|i| @conversion_procs[i] = m}
|
|
142
|
+
m2 = method(:to_application_timestamp)
|
|
143
|
+
m = (v != false) ? lambda{|v| convert_date_time(v, &m2)} : m2
|
|
144
|
+
[7, 12].each{|i| @conversion_procs[i] = m}
|
|
145
|
+
@convert_invalid_date_time = v
|
|
174
146
|
end
|
|
175
|
-
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
def
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
elsif sql.is_a?(Symbol)
|
|
182
|
-
execute_prepared_statement(sql, opts, &block)
|
|
183
|
-
else
|
|
184
|
-
synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
|
|
185
|
-
end
|
|
147
|
+
|
|
148
|
+
# Modify the type translator used for the tinyint type based
|
|
149
|
+
# on the value given.
|
|
150
|
+
def convert_tinyint_to_bool=(v)
|
|
151
|
+
@conversion_procs[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
|
|
152
|
+
@convert_tinyint_to_bool = v
|
|
186
153
|
end
|
|
187
|
-
|
|
154
|
+
|
|
188
155
|
# Return the version of the MySQL server two which we are connecting.
|
|
189
156
|
def server_version(server=nil)
|
|
190
157
|
@server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
|
|
@@ -220,7 +187,7 @@ module Sequel
|
|
|
220
187
|
end
|
|
221
188
|
end
|
|
222
189
|
rescue Mysql::Error => e
|
|
223
|
-
raise_error(e
|
|
190
|
+
raise_error(e)
|
|
224
191
|
ensure
|
|
225
192
|
r.free if r
|
|
226
193
|
# Use up all results to avoid a commands out of sync message.
|
|
@@ -244,10 +211,34 @@ module Sequel
|
|
|
244
211
|
:query
|
|
245
212
|
end
|
|
246
213
|
|
|
214
|
+
# If convert_invalid_date_time is nil, :nil, or :string and
|
|
215
|
+
# the conversion raises an InvalidValue exception, return v
|
|
216
|
+
# if :string and nil otherwise.
|
|
217
|
+
def convert_date_time(v)
|
|
218
|
+
begin
|
|
219
|
+
yield v
|
|
220
|
+
rescue InvalidValue
|
|
221
|
+
case @convert_invalid_date_time
|
|
222
|
+
when nil, :nil
|
|
223
|
+
nil
|
|
224
|
+
when :string
|
|
225
|
+
v
|
|
226
|
+
else
|
|
227
|
+
raise
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
247
232
|
# The MySQL adapter main error class is Mysql::Error
|
|
248
233
|
def database_error_classes
|
|
249
234
|
[Mysql::Error]
|
|
250
235
|
end
|
|
236
|
+
|
|
237
|
+
# Raise a disconnect error if the exception message matches the list
|
|
238
|
+
# of recognized exceptions.
|
|
239
|
+
def disconnect_error?(e, opts)
|
|
240
|
+
super || (e.is_a?(::Mysql::Error) && MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
|
|
241
|
+
end
|
|
251
242
|
|
|
252
243
|
# The database name when using the native adapter is always stored in
|
|
253
244
|
# the :database option.
|
|
@@ -262,97 +253,18 @@ module Sequel
|
|
|
262
253
|
nil
|
|
263
254
|
end
|
|
264
255
|
|
|
265
|
-
# Executes a prepared statement on an available connection. If the
|
|
266
|
-
# prepared statement already exists for the connection and has the same
|
|
267
|
-
# SQL, reuse it, otherwise, prepare the new statement. Because of the
|
|
268
|
-
# usual MySQL stupidity, we are forced to name arguments via separate
|
|
269
|
-
# SET queries. Use @sequel_arg_N (for N starting at 1) for these
|
|
270
|
-
# arguments.
|
|
271
|
-
def execute_prepared_statement(ps_name, opts, &block)
|
|
272
|
-
args = opts[:arguments]
|
|
273
|
-
ps = prepared_statements[ps_name]
|
|
274
|
-
sql = ps.prepared_sql
|
|
275
|
-
synchronize(opts[:server]) do |conn|
|
|
276
|
-
unless conn.prepared_statements[ps_name] == sql
|
|
277
|
-
conn.prepared_statements[ps_name] = sql
|
|
278
|
-
_execute(conn, "PREPARE #{ps_name} FROM '#{::Mysql.quote(sql)}'", opts)
|
|
279
|
-
end
|
|
280
|
-
i = 0
|
|
281
|
-
_execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
|
|
282
|
-
_execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
|
|
286
256
|
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
|
|
287
257
|
def schema_column_type(db_type)
|
|
288
|
-
|
|
258
|
+
convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
|
|
289
259
|
end
|
|
290
260
|
end
|
|
291
261
|
|
|
292
262
|
# Dataset class for MySQL datasets accessed via the native driver.
|
|
293
263
|
class Dataset < Sequel::Dataset
|
|
294
264
|
include Sequel::MySQL::DatasetMethods
|
|
295
|
-
include
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# real database prepared statement and bound variables.
|
|
299
|
-
module CallableStatementMethods
|
|
300
|
-
# Extend given dataset with this module so subselects inside subselects in
|
|
301
|
-
# prepared statements work.
|
|
302
|
-
def subselect_sql(ds)
|
|
303
|
-
ps = ds.to_prepared_statement(:select)
|
|
304
|
-
ps.extend(CallableStatementMethods)
|
|
305
|
-
ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
|
|
306
|
-
ps.prepared_args = prepared_args
|
|
307
|
-
ps.prepared_sql
|
|
308
|
-
end
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Methods for MySQL prepared statements using the native driver.
|
|
312
|
-
module PreparedStatementMethods
|
|
313
|
-
include Sequel::Dataset::UnnumberedArgumentMapper
|
|
314
|
-
|
|
315
|
-
private
|
|
316
|
-
|
|
317
|
-
# Execute the prepared statement with the bind arguments instead of
|
|
318
|
-
# the given SQL.
|
|
319
|
-
def execute(sql, opts={}, &block)
|
|
320
|
-
super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Same as execute, explicit due to intricacies of alias and super.
|
|
324
|
-
def execute_dui(sql, opts={}, &block)
|
|
325
|
-
super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
# Methods for MySQL stored procedures using the native driver.
|
|
330
|
-
module StoredProcedureMethods
|
|
331
|
-
include Sequel::Dataset::StoredProcedureMethods
|
|
332
|
-
|
|
333
|
-
private
|
|
334
|
-
|
|
335
|
-
# Execute the database stored procedure with the stored arguments.
|
|
336
|
-
def execute(sql, opts={}, &block)
|
|
337
|
-
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# Same as execute, explicit due to intricacies of alias and super.
|
|
341
|
-
def execute_dui(sql, opts={}, &block)
|
|
342
|
-
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
# MySQL is different in that it supports prepared statements but not bound
|
|
347
|
-
# variables outside of prepared statements. The default implementation
|
|
348
|
-
# breaks the use of subselects in prepared statements, so extend the
|
|
349
|
-
# temporary prepared statement that this creates with a module that
|
|
350
|
-
# fixes it.
|
|
351
|
-
def call(type, bind_arguments={}, *values, &block)
|
|
352
|
-
ps = to_prepared_statement(type, values)
|
|
353
|
-
ps.extend(CallableStatementMethods)
|
|
354
|
-
ps.call(bind_arguments, &block)
|
|
355
|
-
end
|
|
265
|
+
include Sequel::MySQL::PreparedStatements::DatasetMethods
|
|
266
|
+
|
|
267
|
+
Database::DatasetClass = self
|
|
356
268
|
|
|
357
269
|
# Delete rows matching this dataset
|
|
358
270
|
def delete
|
|
@@ -365,11 +277,12 @@ module Sequel
|
|
|
365
277
|
def fetch_rows(sql, &block)
|
|
366
278
|
execute(sql) do |r|
|
|
367
279
|
i = -1
|
|
280
|
+
cps = db.conversion_procs
|
|
368
281
|
cols = r.fetch_fields.map do |f|
|
|
369
282
|
# Pretend tinyint is another integer type if its length is not 1, to
|
|
370
283
|
# avoid casting to boolean if Sequel::MySQL.convert_tinyint_to_bool
|
|
371
284
|
# is set.
|
|
372
|
-
type_proc = f.type == 1 && f.length != 1 ?
|
|
285
|
+
type_proc = f.type == 1 && f.length != 1 ? cps[2] : cps[f.type]
|
|
373
286
|
[output_identifier(f.name), type_proc, i+=1]
|
|
374
287
|
end
|
|
375
288
|
@columns = cols.map{|c| c.first}
|
|
@@ -395,18 +308,6 @@ module Sequel
|
|
|
395
308
|
execute_dui(insert_sql(*values)){|c| return c.insert_id}
|
|
396
309
|
end
|
|
397
310
|
|
|
398
|
-
# Store the given type of prepared statement in the associated database
|
|
399
|
-
# with the given name.
|
|
400
|
-
def prepare(type, name=nil, *values)
|
|
401
|
-
ps = to_prepared_statement(type, values)
|
|
402
|
-
ps.extend(PreparedStatementMethods)
|
|
403
|
-
if name
|
|
404
|
-
ps.prepared_statement_name = name
|
|
405
|
-
db.prepared_statements[name] = ps
|
|
406
|
-
end
|
|
407
|
-
ps
|
|
408
|
-
end
|
|
409
|
-
|
|
410
311
|
# Replace (update or insert) the matching row.
|
|
411
312
|
def replace(*args)
|
|
412
313
|
execute_dui(replace_sql(*args)){|c| return c.insert_id}
|
|
@@ -450,11 +351,6 @@ module Sequel
|
|
|
450
351
|
"'#{::Mysql.quote(v)}'"
|
|
451
352
|
end
|
|
452
353
|
|
|
453
|
-
# Extend the dataset with the MySQL stored procedure methods.
|
|
454
|
-
def prepare_extend_sproc(ds)
|
|
455
|
-
ds.extend(StoredProcedureMethods)
|
|
456
|
-
end
|
|
457
|
-
|
|
458
354
|
# Yield each row of the given result set r with columns cols
|
|
459
355
|
# as a hash with symbol keys
|
|
460
356
|
def yield_rows(r, cols)
|