sequel 3.7.0 → 3.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG +50 -0
  2. data/doc/advanced_associations.rdoc +4 -3
  3. data/doc/release_notes/3.7.0.txt +2 -2
  4. data/doc/release_notes/3.8.0.txt +151 -0
  5. data/lib/sequel/adapters/jdbc.rb +10 -2
  6. data/lib/sequel/adapters/jdbc/h2.rb +14 -0
  7. data/lib/sequel/adapters/mysql.rb +36 -26
  8. data/lib/sequel/adapters/postgres.rb +27 -19
  9. data/lib/sequel/adapters/shared/mssql.rb +12 -4
  10. data/lib/sequel/adapters/shared/mysql.rb +16 -0
  11. data/lib/sequel/connection_pool.rb +178 -57
  12. data/lib/sequel/database.rb +60 -12
  13. data/lib/sequel/database/schema_generator.rb +1 -2
  14. data/lib/sequel/dataset.rb +10 -1
  15. data/lib/sequel/dataset/actions.rb +4 -8
  16. data/lib/sequel/dataset/convenience.rb +9 -2
  17. data/lib/sequel/dataset/query.rb +2 -5
  18. data/lib/sequel/dataset/sql.rb +0 -1
  19. data/lib/sequel/exceptions.rb +3 -3
  20. data/lib/sequel/metaprogramming.rb +4 -18
  21. data/lib/sequel/model/associations.rb +2 -2
  22. data/lib/sequel/model/base.rb +15 -18
  23. data/lib/sequel/model/default_inflections.rb +0 -1
  24. data/lib/sequel/model/plugins.rb +3 -2
  25. data/lib/sequel/plugins/boolean_readers.rb +3 -2
  26. data/lib/sequel/plugins/identity_map.rb +9 -0
  27. data/lib/sequel/plugins/validation_helpers.rb +8 -1
  28. data/lib/sequel/sql.rb +21 -11
  29. data/lib/sequel/version.rb +1 -1
  30. data/spec/adapters/mssql_spec.rb +7 -1
  31. data/spec/adapters/mysql_spec.rb +22 -0
  32. data/spec/core/connection_pool_spec.rb +211 -3
  33. data/spec/core/core_sql_spec.rb +7 -0
  34. data/spec/core/database_spec.rb +159 -7
  35. data/spec/core/dataset_spec.rb +33 -0
  36. data/spec/core/spec_helper.rb +1 -0
  37. data/spec/extensions/boolean_readers_spec.rb +6 -0
  38. data/spec/extensions/identity_map_spec.rb +29 -1
  39. data/spec/extensions/inflector_spec.rb +0 -1
  40. data/spec/extensions/validation_helpers_spec.rb +23 -0
  41. data/spec/integration/type_test.rb +1 -1
  42. metadata +131 -129
data/CHANGELOG CHANGED
@@ -1,3 +1,53 @@
1
+ === 3.8.0 (2010-01-04)
2
+
3
+ * Catch cases in the postgres adapter where exceptions weren't converted or raised appropriately (jeremyevans)
4
+
5
+ * Don't double escape backslashes in string literals in the mssql shared adapter (john_firebaugh)
6
+
7
+ * Fix order of ORDER and HAVING clauses in the mssql shared adapter (mluu)
8
+
9
+ * Add validates_type to the validation_helpers plugin (mluu)
10
+
11
+ * Attempt to detect database disconnects in the JDBC adapter (john_firebaugh)
12
+
13
+ * Add Sequel::SQL::Expression#==, so arbtirary expressions can be compared by value (dlee)
14
+
15
+ * Respect the :size option for the generic File type on MySQL to create tinyblob, mediumblob, and longblob (ibc)
16
+
17
+ * Don't use the OUTPUT clause on SQL Server versions that don't support it (pre-2005) (jeremyevans) (#281)
18
+
19
+ * Raise DatabaseConnectionErrors in the single-threaded connection pool if unable to connect (jeremyevans)
20
+
21
+ * Fix handling of non-existent server in single-threaded connection pool (jeremyevans)
22
+
23
+ * Default to using mysqlplus driver in the native mysql adapter, fall back to mysql driver (ibc, jeremyevans)
24
+
25
+ * Handle 64-bit integers in JDBC prepared statements (paulfras)
26
+
27
+ * Improve blob support when using the H2 JDBC subadapter (nullstyle, jeremyevans, paulfras)
28
+
29
+ * Add Database#each_server, which yields a new Database object for each server in the connection pool which is connected to only that server (jeremyevans)
30
+
31
+ * Add Dataset#each_server, which yields a dataset for each server in the connection pool which is will execute on that server (jeremyevans)
32
+
33
+ * Remove meta_eval and metaclass private methods from Sequel::Metaprogramming (jeremyevans)
34
+
35
+ * Merge Dataset::FROM_SELF_KEEP_OPTS into Dataset::NON_SQL_OPTIONS (jeremyevans)
36
+
37
+ * Add Database#remove_servers for removing servers from the pool on the fly (jeremyevans)
38
+
39
+ * When disconnecting servers, if there are any connections to the server currently in use, schedule them to be disconnected (jeremyevans)
40
+
41
+ * Allow disconnecting specific server(s)/shard(s) in Database#disconnect via a :servers option (jeremyevans)
42
+
43
+ * Handle multiple statements in a single query in the native MySQL adapter in all cases, not just when selecting via Dataset#each (jeremyevans)
44
+
45
+ * In the boolean_readers plugin, don't raise an error if the model's columns can't be determined (jeremyevans)
46
+
47
+ * In the identity_map plugin, remove instances from the cache if they are deleted/destroyed (jeremyevans)
48
+
49
+ * Add Database#add_servers, for adding new servers/shards on the fly (chuckremes, jeremyevans)
50
+
1
51
  === 3.7.0 (2009-12-01)
2
52
 
3
53
  * Add Dataset#sequence to the shared Oracle Adapter, for returning autogenerated primary key values on insert (jeremyevans) (#280)
@@ -476,8 +476,8 @@ Let's say you want to store a tree relationship in your database, it's pretty
476
476
  simple:
477
477
 
478
478
  class Node < Sequel::Model
479
- many_to_one :parent
480
- one_to_many :children, :key=>:parent_id
479
+ many_to_one :parent, :class=>self
480
+ one_to_many :children, :key=>:parent_id, :class=>self
481
481
  end
482
482
 
483
483
  You can easily get a node's parent with node.parent, and a node's children with
@@ -492,7 +492,8 @@ What if you want to get all ancestors up to the root node, or all descendents,
492
492
  without knowing the depth of the tree?
493
493
 
494
494
  class Node < Sequel::Model
495
- many_to_one :ancestors, :eager_loader=>(proc do |key_hash, nodes, associations|
495
+ many_to_one :ancestors, :class=>self,
496
+ :eager_loader=>(proc do |key_hash, nodes, associations|
496
497
  # Handle cases where the root node has the same parent_id as primary_key
497
498
  # and also when it is NULL
498
499
  non_root_nodes = nodes.reject do |n|
@@ -97,8 +97,8 @@ New Features
97
97
  database could be changed between retrieving the model object and
98
98
  updating it.
99
99
 
100
- * The Dataest #union, #intersect, and #except methods now accept an
101
- :alias option that it used as the alias for the returned dataset.
100
+ * The Dataset #union, #intersect, and #except methods now accept an
101
+ :alias option which is used as the alias for the returned dataset.
102
102
 
103
103
  DB[:table].union(DB[:old_table], :alias=>:table)
104
104
 
@@ -0,0 +1,151 @@
1
+ New Features
2
+ ------------
3
+
4
+ * Dataset#each_server was added, allowing you to run the same query
5
+ (most likely insert/update/delete) on all shards. This is useful
6
+ if you have a sharded database but have lookup tables that should
7
+ be identical on all shards. It works by yielding copies of the
8
+ current dataset that are tied to each server/shard:
9
+
10
+ DB[:table].filter(:id=>1).each_server do |ds|
11
+ ds.update(:name=>'foo')
12
+ end
13
+
14
+ * Database#each_server was added, allowing you to run schema
15
+ modification methods on all shards. It works by yielding a
16
+ new Sequel::Database object for each shard, that will connect to
17
+ only that shard:
18
+
19
+ DB.each_server do |db|
20
+ db.create_table(:t){Integer :num}
21
+ end
22
+
23
+ * You can now add and remove servers/shards from the connection
24
+ pool while Sequel is running:
25
+
26
+ DB.add_servers(:shard1=>{:host=>'s1'}, :shard2=>{:host=>'s2'})
27
+ DB.remove_servers(:shard1, :shard2)
28
+
29
+ * When you attempt to disconnect from a server that has connections
30
+ currently in use, Sequel will now schedule those connections to
31
+ be disconnected when they are returned to the pool. Previously,
32
+ Sequel disconnected available connections, but ignored connections
33
+ currently in use, so it wasn't possible to guarantee complete
34
+ disconnection from the server. Even with this new feature, you can
35
+ only guarantee eventual disconnection, since disconnection of
36
+ connections in use happens asynchronously.
37
+
38
+ * Database#disconnect now accepts a :servers option specifying the
39
+ server(s) from which to disconnect. This should be a symbol or
40
+ array of symbols representing servers/shards. Only those specified
41
+ will be disconnected:
42
+
43
+ DB.disconnect(:servers=>[:shard1, :shard2])
44
+
45
+ * A validates_type validation was added to the validation_helpers
46
+ plugin. It allows you to check that a given column contains
47
+ the correct type. I can be helpful if you are also using the
48
+ serialization plugin to store serialized ruby objects, by making
49
+ sure that the objects are of the correct type (e.g. Hash):
50
+
51
+ def validate
52
+ validates_type(Hash, :options)
53
+ end
54
+
55
+ * Sequel::SQL::Expression#== is now supported for all expressions:
56
+
57
+ :column.qualify(:table).cast(:type) == \
58
+ :column.qualify(:table).cast(:type)
59
+ # => true
60
+ :column.qualify(:table).cast(:type) == \
61
+ :other_column.qualify(:table).cast(:type)
62
+ # => false
63
+
64
+ * When using the generic File type to create blob columns on
65
+ MySQL, you can specify the specific database type by using the
66
+ :size option (with :tiny, :medium, and :long values recognized):
67
+
68
+ DB.create_table(:docs){File :body, :size=>:long} # longblob
69
+
70
+ * The mysql adapter will now default to using mysqlplus, falling
71
+ back to use mysql. mysqlplus is significantly better for threaded
72
+ code because queries do not block the entire interpreter.
73
+
74
+ * The JDBC adapter is now able to detect certain types of disconnect
75
+ errors.
76
+
77
+ * ConnectionPool.servers and Database.servers were added, which
78
+ return an array of symbols specifying the servers/shards in use.
79
+
80
+ Other Improvements
81
+ ------------------
82
+
83
+ * The single-threaded connection pool now raises
84
+ DatabaseConnectionErrors if unable to connect, so it now operates
85
+ more similarly to the default connection pool.
86
+
87
+ * The single-threaded connection pool now operates more similar
88
+ to the default connection pool when given a nonexistent server.
89
+
90
+ * PGErrors are now correctly converted to DatabaseErrors in the
91
+ postgres adapter when preparing statements or executing prepared
92
+ statements.
93
+
94
+ * DatabaseDisconnectErrors are now raised correctly in the postgres
95
+ adapter if the connection status is not OK after a query raises an
96
+ error.
97
+
98
+ * In the mysql adapter, multiple statements in a single query should
99
+ now be handled correctly in the all cases, not just when using
100
+ Dataset#each. So you can now submit multiple queries in a single
101
+ string to Database#run.
102
+
103
+ * Model object creation on Microsoft SQL Server 2000 once again
104
+ works correctly. Previously, an optimization was used that was
105
+ only supported on 2005+.
106
+
107
+ * Backslashes are no longer doubled inside string literals when
108
+ connecting to Microsoft SQL Server.
109
+
110
+ * The ORDER clause now correctly comes after the HAVING clause on
111
+ Microsoft SQL Server.
112
+
113
+ * Sequel now checks that there is an active transaction before
114
+ rolling back transactions on Microsoft SQL Server, since
115
+ there are cases where Microsoft SQL Server will roll back
116
+ transactions implicitly.
117
+
118
+ * Blobs are now handled correctly when connecting to H2.
119
+
120
+ * 64-bit integers are now handled correctly in JDBC prepared
121
+ statements.
122
+
123
+ * In the boolean_readers plugin, correctly handle columns not in
124
+ the db_schema, and don't raise an error if the model's columns
125
+ can't be determined.
126
+
127
+ * In the identity_map plugin, remove instances from the cache if they
128
+ are deleted or destroyed.
129
+
130
+ Backwards Compatibility
131
+ -----------------------
132
+
133
+ * Dataset::FROM_SELF_KEEP_OPTS was merged into
134
+ Dataset::NON_SQL_OPTIONS. While used in different places, they
135
+ were used for the same purpose, and entries missing from one should
136
+ have been included in the other.
137
+
138
+ * The connection pool internals changed substantially. Now,
139
+ ConnectionPool #allocated and #available_connections will return
140
+ nil instead of an array or hash if they are called with a
141
+ nonexistent server. These are generally only used internally,
142
+ though they are part of the public API. #created_count and #size
143
+ still return the size of the :default server when called with a
144
+ nonexistent server, though.
145
+
146
+ * The meta_eval and metaclass private methods were removed from
147
+ Sequel::MetaProgramming (only the meta_def public method remains).
148
+ If you want these methods, use the metaid gem.
149
+
150
+ * The irregular ox->oxen pluralization rule was removed from the
151
+ default inflections, as it screws up the more common box->boxes.
@@ -322,7 +322,13 @@ module Sequel
322
322
  def metadata(*args, &block)
323
323
  synchronize{|c| metadata_dataset.send(:process_result_set, c.getMetaData.send(*args), &block)}
324
324
  end
325
-
325
+
326
+ # Treat SQLExceptions with a "Connection Error" SQLState as disconnects
327
+ def raise_error(exception, opts={})
328
+ cause = exception.respond_to?(:cause) ? exception.cause : exception
329
+ super(exception, {:disconnect => cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/}.merge(opts))
330
+ end
331
+
326
332
  # Close the given statement when removing the transaction
327
333
  def remove_transaction(stmt)
328
334
  stmt.close if stmt && !supports_savepoints?
@@ -336,7 +342,7 @@ module Sequel
336
342
  def set_ps_arg(cps, arg, i)
337
343
  case arg
338
344
  when Integer
339
- cps.setInt(i, arg)
345
+ cps.setLong(i, arg)
340
346
  when Sequel::SQL::Blob
341
347
  cps.setBytes(i, arg.to_java_bytes)
342
348
  when String
@@ -492,6 +498,8 @@ module Sequel
492
498
  BigDecimal.new(v.to_string)
493
499
  when Java::byte[]
494
500
  Sequel::SQL::Blob.new(String.from_java_bytes(v))
501
+ when Java::JavaSQL::Blob
502
+ convert_type(v.getBytes(0, v.length))
495
503
  else
496
504
  v
497
505
  end
@@ -105,6 +105,20 @@ module Sequel
105
105
 
106
106
  private
107
107
 
108
+ # H2 expects hexadecimal strings for blob values
109
+ def literal_blob(v)
110
+ literal_string v.unpack("H*").first
111
+ end
112
+
113
+ def convert_type(v)
114
+ case v
115
+ when Java::OrgH2Jdbc::JdbcClob
116
+ convert_type(v.getSubString(1, v.length))
117
+ else
118
+ super(v)
119
+ end
120
+ end
121
+
108
122
  def select_clause_methods
109
123
  SELECT_CLAUSE_METHODS
110
124
  end
@@ -1,5 +1,10 @@
1
- require 'mysql'
1
+ begin
2
+ require "mysqlplus"
3
+ rescue LoadError
4
+ require 'mysql'
5
+ end
2
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
+
3
8
  Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
4
9
 
5
10
  module Sequel
@@ -160,35 +165,40 @@ module Sequel
160
165
  r = conn.query(sql)
161
166
  if opts[:type] == :select
162
167
  yield r if r
163
- if conn.respond_to?(:next_result) && conn.next_result
164
- loop do
165
- if r
166
- r.free
167
- r = nil
168
- end
169
- begin
170
- r = conn.use_result
171
- rescue Mysql::Error
172
- break
173
- end
174
- yield r
175
- break unless conn.next_result
168
+ elsif block_given?
169
+ yield conn
170
+ end
171
+ if conn.respond_to?(:more_results?)
172
+ while conn.more_results? do
173
+ if r
174
+ r.free
175
+ r = nil
176
176
  end
177
+ begin
178
+ conn.next_result
179
+ r = conn.use_result
180
+ rescue Mysql::Error => e
181
+ raise_error(e, :disconnect=>true) if MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message)
182
+ break
183
+ end
184
+ yield r if opts[:type] == :select
177
185
  end
178
- else
179
- yield conn if block_given?
180
186
  end
181
187
  rescue Mysql::Error => e
182
188
  raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
183
189
  ensure
184
- if r
185
- r.free
186
- # Use up all results to avoid a commands out of sync message.
187
- if conn.respond_to?(:next_result)
188
- while conn.next_result
190
+ r.free if r
191
+ # Use up all results to avoid a commands out of sync message.
192
+ if conn.respond_to?(:more_results?)
193
+ while conn.more_results? do
194
+ begin
195
+ conn.next_result
189
196
  r = conn.use_result
190
- r.free if r
197
+ rescue Mysql::Error => e
198
+ raise_error(e, :disconnect=>true) if MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message)
199
+ break
191
200
  end
201
+ r.free if r
192
202
  end
193
203
  end
194
204
  end
@@ -314,7 +324,7 @@ module Sequel
314
324
 
315
325
  # Delete rows matching this dataset
316
326
  def delete
317
- execute_dui(delete_sql){|c| c.affected_rows}
327
+ execute_dui(delete_sql){|c| return c.affected_rows}
318
328
  end
319
329
 
320
330
  # Yield all rows matching this dataset. If the dataset is set to
@@ -344,7 +354,7 @@ module Sequel
344
354
 
345
355
  # Insert a new value into this dataset
346
356
  def insert(*values)
347
- execute_dui(insert_sql(*values)){|c| c.insert_id}
357
+ execute_dui(insert_sql(*values)){|c| return c.insert_id}
348
358
  end
349
359
 
350
360
  # Store the given type of prepared statement in the associated database
@@ -361,7 +371,7 @@ module Sequel
361
371
 
362
372
  # Replace (update or insert) the matching row.
363
373
  def replace(*args)
364
- execute_dui(replace_sql(*args)){|c| c.insert_id}
374
+ execute_dui(replace_sql(*args)){|c| return c.insert_id}
365
375
  end
366
376
 
367
377
  # Makes each yield arrays of rows, with each array containing the rows
@@ -382,7 +392,7 @@ module Sequel
382
392
 
383
393
  # Update the matching rows.
384
394
  def update(values={})
385
- execute_dui(update_sql(values)){|c| c.affected_rows}
395
+ execute_dui(update_sql(values)){|c| return c.affected_rows}
386
396
  end
387
397
 
388
398
  private
@@ -138,24 +138,29 @@ module Sequel
138
138
  end
139
139
  @prepared_statements = {} if SEQUEL_POSTGRES_USES_PG
140
140
  end
141
-
142
- # Execute the given SQL with this connection. If a block is given,
143
- # yield the results, otherwise, return the number of changed rows.
144
- def execute(sql, args=nil)
145
- q = nil
141
+
142
+ # Raise a Sequel::DatabaseDisconnectError if a PGError is raised and
143
+ # the connection status cannot be determined or it is not OK.
144
+ def check_disconnect_errors
146
145
  begin
147
- q = args ? async_exec(sql, args) : async_exec(sql)
148
- rescue PGError => e
146
+ yield
147
+ rescue PGError =>e
149
148
  begin
150
149
  s = status
151
150
  rescue PGError
152
151
  raise Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)
153
152
  end
154
153
  status_ok = (s == Adapter::CONNECTION_OK)
155
- status_ok ? raise : Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)
154
+ status_ok ? raise : raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError))
156
155
  ensure
157
156
  block if status_ok
158
157
  end
158
+ end
159
+
160
+ # Execute the given SQL with this connection. If a block is given,
161
+ # yield the results, otherwise, return the number of changed rows.
162
+ def execute(sql, args=nil)
163
+ q = check_disconnect_errors{args ? async_exec(sql, args) : async_exec(sql)}
159
164
  begin
160
165
  block_given? ? yield(q) : q.cmd_tuples
161
166
  ensure
@@ -218,13 +223,10 @@ module Sequel
218
223
 
219
224
  # Execute the given SQL with the given args on an available connection.
220
225
  def execute(sql, opts={}, &block)
221
- return execute_prepared_statement(sql, opts, &block) if Symbol === sql
222
- begin
226
+ check_database_errors do
227
+ return execute_prepared_statement(sql, opts, &block) if Symbol === sql
223
228
  log_info(sql, opts[:arguments])
224
229
  synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
225
- rescue => e
226
- log_info(e.message)
227
- raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
228
230
  end
229
231
  end
230
232
 
@@ -232,19 +234,25 @@ module Sequel
232
234
  # automatically generated).
233
235
  def execute_insert(sql, opts={})
234
236
  return execute(sql, opts) if Symbol === sql
235
- begin
237
+ check_database_errors do
236
238
  log_info(sql, opts[:arguments])
237
239
  synchronize(opts[:server]) do |conn|
238
240
  conn.execute(sql, opts[:arguments])
239
241
  insert_result(conn, opts[:table], opts[:values])
240
242
  end
241
- rescue => e
242
- log_info(e.message)
243
- raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
244
243
  end
245
244
  end
246
245
 
247
246
  private
247
+
248
+ # Convert exceptions raised from the block into DatabaseErrors.
249
+ def check_database_errors
250
+ begin
251
+ yield
252
+ rescue => e
253
+ raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
254
+ end
255
+ end
248
256
 
249
257
  # PostgreSQL doesn't need the connection pool to convert exceptions.
250
258
  def connection_pool_default_options
@@ -281,10 +289,10 @@ module Sequel
281
289
  end
282
290
  conn.prepared_statements[ps_name] = sql
283
291
  log_info("PREPARE #{ps_name} AS #{sql}")
284
- conn.prepare(ps_name, sql)
292
+ conn.check_disconnect_errors{conn.prepare(ps_name, sql)}
285
293
  end
286
294
  log_info("EXECUTE #{ps_name}", args)
287
- q = conn.exec_prepared(ps_name, args)
295
+ q = conn.check_disconnect_errors{conn.exec_prepared(ps_name, args)}
288
296
  if opts[:table] && opts[:values]
289
297
  insert_result(conn, opts[:table], opts[:values])
290
298
  else