sequel 3.0.0 → 3.1.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.
Files changed (87) hide show
  1. data/CHANGELOG +100 -0
  2. data/README.rdoc +3 -3
  3. data/bin/sequel +102 -19
  4. data/doc/reflection.rdoc +83 -0
  5. data/doc/release_notes/3.1.0.txt +406 -0
  6. data/lib/sequel/adapters/ado.rb +11 -0
  7. data/lib/sequel/adapters/amalgalite.rb +5 -20
  8. data/lib/sequel/adapters/do.rb +44 -36
  9. data/lib/sequel/adapters/firebird.rb +29 -43
  10. data/lib/sequel/adapters/jdbc.rb +17 -27
  11. data/lib/sequel/adapters/mysql.rb +35 -40
  12. data/lib/sequel/adapters/odbc.rb +4 -23
  13. data/lib/sequel/adapters/oracle.rb +22 -19
  14. data/lib/sequel/adapters/postgres.rb +6 -15
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +29 -10
  17. data/lib/sequel/adapters/shared/oracle.rb +6 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +28 -72
  19. data/lib/sequel/adapters/shared/sqlite.rb +5 -3
  20. data/lib/sequel/adapters/sqlite.rb +5 -20
  21. data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
  22. data/lib/sequel/adapters/utils/unsupported.rb +0 -12
  23. data/lib/sequel/core.rb +12 -3
  24. data/lib/sequel/core_sql.rb +1 -8
  25. data/lib/sequel/database.rb +107 -43
  26. data/lib/sequel/database/schema_generator.rb +1 -0
  27. data/lib/sequel/database/schema_methods.rb +38 -4
  28. data/lib/sequel/dataset.rb +6 -0
  29. data/lib/sequel/dataset/convenience.rb +2 -2
  30. data/lib/sequel/dataset/graph.rb +2 -2
  31. data/lib/sequel/dataset/prepared_statements.rb +3 -8
  32. data/lib/sequel/dataset/sql.rb +93 -19
  33. data/lib/sequel/extensions/blank.rb +2 -1
  34. data/lib/sequel/extensions/inflector.rb +4 -3
  35. data/lib/sequel/extensions/migration.rb +13 -2
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pretty_table.rb +4 -0
  38. data/lib/sequel/extensions/query.rb +4 -0
  39. data/lib/sequel/extensions/schema_dumper.rb +100 -24
  40. data/lib/sequel/extensions/string_date_time.rb +3 -4
  41. data/lib/sequel/model.rb +2 -1
  42. data/lib/sequel/model/associations.rb +96 -38
  43. data/lib/sequel/model/base.rb +14 -14
  44. data/lib/sequel/model/plugins.rb +32 -21
  45. data/lib/sequel/plugins/caching.rb +13 -15
  46. data/lib/sequel/plugins/identity_map.rb +107 -0
  47. data/lib/sequel/plugins/lazy_attributes.rb +65 -0
  48. data/lib/sequel/plugins/many_through_many.rb +188 -0
  49. data/lib/sequel/plugins/schema.rb +13 -0
  50. data/lib/sequel/plugins/serialization.rb +53 -37
  51. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  52. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  53. data/lib/sequel/plugins/validation_class_methods.rb +28 -7
  54. data/lib/sequel/plugins/validation_helpers.rb +31 -24
  55. data/lib/sequel/sql.rb +16 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/ado_spec.rb +47 -1
  58. data/spec/adapters/firebird_spec.rb +39 -36
  59. data/spec/adapters/mysql_spec.rb +25 -9
  60. data/spec/adapters/postgres_spec.rb +11 -24
  61. data/spec/core/database_spec.rb +54 -13
  62. data/spec/core/dataset_spec.rb +147 -29
  63. data/spec/core/object_graph_spec.rb +6 -1
  64. data/spec/core/schema_spec.rb +34 -0
  65. data/spec/core/spec_helper.rb +0 -2
  66. data/spec/extensions/caching_spec.rb +7 -0
  67. data/spec/extensions/identity_map_spec.rb +158 -0
  68. data/spec/extensions/lazy_attributes_spec.rb +113 -0
  69. data/spec/extensions/many_through_many_spec.rb +813 -0
  70. data/spec/extensions/migration_spec.rb +4 -4
  71. data/spec/extensions/schema_dumper_spec.rb +114 -13
  72. data/spec/extensions/schema_spec.rb +19 -3
  73. data/spec/extensions/serialization_spec.rb +28 -0
  74. data/spec/extensions/single_table_inheritance_spec.rb +25 -1
  75. data/spec/extensions/spec_helper.rb +2 -7
  76. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  77. data/spec/extensions/validation_class_methods_spec.rb +10 -5
  78. data/spec/integration/dataset_test.rb +39 -6
  79. data/spec/integration/eager_loader_test.rb +7 -7
  80. data/spec/integration/spec_helper.rb +0 -1
  81. data/spec/integration/transaction_test.rb +28 -1
  82. data/spec/model/association_reflection_spec.rb +29 -3
  83. data/spec/model/associations_spec.rb +1 -0
  84. data/spec/model/eager_loading_spec.rb +70 -1
  85. data/spec/model/plugins_spec.rb +236 -50
  86. data/spec/model/spec_helper.rb +0 -2
  87. metadata +18 -5
@@ -25,10 +25,21 @@ module Sequel
25
25
  end
26
26
  end
27
27
 
28
+ # Connect to the database. In addition to the usual database options,
29
+ # the following option has effect:
30
+ #
31
+ # * :command_timout - Sets the time in seconds to wait while attempting
32
+ # to execute a command before cancelling the attempt and generating
33
+ # an error. Specificially, it sets the ADO CommandTimeout property.
34
+ # If this property is not set, the default of 30 seconds is used.
35
+ # * :provider - Sets the Provider of this ADO connection (for example, "SQLOLEDB")
36
+
28
37
  def connect(server)
29
38
  opts = server_opts(server)
30
39
  s = "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
31
40
  handle = WIN32OLE.new('ADODB.Connection')
41
+ handle.CommandTimeout = opts[:command_timeout] if opts[:command_timeout]
42
+ handle.Provider = opts[:provider] if opts[:provider]
32
43
  handle.Open(s)
33
44
  handle
34
45
  end
@@ -119,26 +119,6 @@ module Sequel
119
119
  _execute(sql, opts){|conn| conn.first_value_from(sql)}
120
120
  end
121
121
 
122
- # Use the native driver transaction method if there isn't already a transaction
123
- # in progress on the connection, always yielding a connection inside a transaction
124
- # transaction.
125
- def transaction(opts={})
126
- synchronize(opts[:server]) do |conn|
127
- return yield(conn) if conn.in_transaction?
128
- begin
129
- result = nil
130
- log_info('Transaction.begin')
131
- conn.transaction{result = yield(conn)}
132
- result
133
- rescue ::Exception => e
134
- log_info('Transaction.rollback')
135
- transaction_error(e, ::Amalgalite::Error, ::Amalgalite::SQLite3::Error)
136
- ensure
137
- log_info('Transaction.commit') unless e
138
- end
139
- end
140
- end
141
-
142
122
  private
143
123
 
144
124
  # Log the SQL and yield an available connection. Rescue
@@ -162,6 +142,11 @@ module Sequel
162
142
  o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
163
143
  o
164
144
  end
145
+
146
+ # Both main error classes that Amalgalite raises
147
+ def database_error_classes
148
+ [::Amalgalite::Error, ::Amalgalite::SQLite3::Error]
149
+ end
165
150
 
166
151
  # Disconnect given connections from the database.
167
152
  def disconnect_connection(c)
@@ -116,37 +116,6 @@ module Sequel
116
116
  uri.split(":").first
117
117
  end
118
118
 
119
- # Use DataObject's transaction support for transactions. This
120
- # only supports single level transactions, and it always prepares
121
- # transactions and commits them immediately after. It's wasteful,
122
- # but required by DataObject's API.
123
- def transaction(opts={})
124
- th = Thread.current
125
- synchronize(opts[:server]) do |conn|
126
- return yield(conn) if @transactions.include?(th)
127
- t = ::DataObjects::Transaction.create_for_uri(uri)
128
- t.instance_variable_get(:@connection).close
129
- t.instance_variable_set(:@connection, conn)
130
- begin
131
- log_info("Transaction.begin")
132
- t.begin
133
- @transactions << th
134
- yield(conn)
135
- rescue Exception => e
136
- log_info("Transaction.rollback")
137
- t.rollback
138
- transaction_error(e)
139
- ensure
140
- unless e
141
- log_info("Transaction.commit")
142
- t.prepare
143
- t.commit
144
- end
145
- @transactions.delete(th)
146
- end
147
- end
148
- end
149
-
150
119
  # Return the DataObjects URI for the Sequel URI, removing the do:
151
120
  # prefix.
152
121
  def uri(opts={})
@@ -155,22 +124,61 @@ module Sequel
155
124
  end
156
125
 
157
126
  private
127
+
128
+ # DataObjects uses a special transaction object to keep track of
129
+ # transactions. Unfortunately, it tries to create a new connection
130
+ # to do a transaction. So we close the connection created and
131
+ # substitute our own.
132
+ def begin_transaction(conn)
133
+ log_info(TRANSACTION_BEGIN)
134
+ t = ::DataObjects::Transaction.create_for_uri(uri)
135
+ t.instance_variable_get(:@connection).close
136
+ t.instance_variable_set(:@connection, conn)
137
+ t.begin
138
+ t
139
+ end
140
+
141
+ # DataObjects requires transactions be prepared before being
142
+ # committed, so we do that.
143
+ def commit_transaction(t)
144
+ log_info(TRANSACTION_ROLLBACK)
145
+ t.prepare
146
+ t.commit
147
+ end
148
+
149
+ # Method to call on a statement object to execute SQL that does
150
+ # not return any rows.
151
+ def connection_execute_method
152
+ :execute_non_query
153
+ end
154
+
155
+ # The DataObjects adapter should convert exceptions by default.
156
+ def connection_pool_default_options
157
+ super.merge(:pool_convert_exceptions=>false)
158
+ end
158
159
 
159
160
  # Close the given database connection.
160
161
  def disconnect_connection(c)
161
162
  c.close
162
163
  end
163
164
 
165
+ # Execute SQL on the connection by creating a command first
166
+ def log_connection_execute(conn, sql)
167
+ log_info(sql)
168
+ conn.create_command(sql).execute_non_query
169
+ end
170
+
171
+ # We use the transactions rollback method to rollback.
172
+ def rollback_transaction(t)
173
+ log_info(TRANSACTION_COMMIT)
174
+ t.rollback
175
+ end
176
+
164
177
  # Allow extending the given connection when it is first created.
165
178
  # By default, just returns the connection.
166
179
  def setup_connection(conn)
167
180
  conn
168
181
  end
169
-
170
- # The DataObjects adapter should convert exceptions by default.
171
- def connection_pool_default_options
172
- super.merge(:pool_convert_exceptions=>false)
173
- end
174
182
  end
175
183
 
176
184
  # Dataset class for Sequel::DataObjects::Database objects.
@@ -31,26 +31,6 @@ module Sequel
31
31
  conn
32
32
  end
33
33
 
34
- # Creates a table with the columns given in the provided block:
35
- #
36
- # DB.create_table :posts do
37
- # primary_key :id, :serial
38
- # column :title, :text
39
- # column :content, :text
40
- # index :title
41
- # end
42
- #
43
- # See Schema::Generator.
44
- # Firebird gets an override because of the mess of creating a
45
- # generator for auto-incrementing primary keys.
46
- def create_table(name, options={}, &block)
47
- options = {:generator=>options} if options.is_a?(Schema::Generator)
48
- generator = options[:generator] || Schema::Generator.new(self, &block)
49
- drop_statement, create_statements = create_table_sql_list(name, generator, options)
50
- (execute_ddl(drop_statement) rescue nil) if drop_statement
51
- (create_statements + index_sql_list(name, generator.indexes)).each{|sql| execute_ddl(sql)}
52
- end
53
-
54
34
  def create_trigger(*args)
55
35
  self << create_trigger_sql(*args)
56
36
  end
@@ -103,28 +83,6 @@ module Sequel
103
83
  block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"])}
104
84
  end
105
85
 
106
- def transaction(opts={})
107
- synchronize(opts[:server]) do |conn|
108
- return yield(conn) if @transactions.include?(Thread.current)
109
- log_info("Transaction.begin")
110
- conn.transaction
111
- begin
112
- @transactions << Thread.current
113
- yield(conn)
114
- rescue ::Exception => e
115
- log_info("Transaction.rollback")
116
- conn.rollback
117
- transaction_error(e, Fb::Error)
118
- ensure
119
- unless e
120
- log_info("Transaction.commit")
121
- conn.commit
122
- end
123
- @transactions.delete(Thread.current)
124
- end
125
- end
126
- end
127
-
128
86
  private
129
87
 
130
88
  # Use Firebird specific syntax for add column
@@ -147,10 +105,29 @@ module Sequel
147
105
  AUTO_INCREMENT
148
106
  end
149
107
 
108
+ def begin_transaction(conn)
109
+ log_info(TRANSACTION_BEGIN)
110
+ conn.transaction
111
+ conn
112
+ end
113
+
114
+ def commit_transaction(conn)
115
+ log_info(TRANSACTION_COMMIT)
116
+ conn.commit
117
+ end
118
+
150
119
  def create_sequence_sql(name, opts={})
151
120
  "CREATE SEQUENCE #{quote_identifier(name)}"
152
121
  end
153
122
 
123
+ # Firebird gets an override because of the mess of creating a
124
+ # sequence and trigger for auto-incrementing primary keys.
125
+ def create_table_from_generator(name, generator, options)
126
+ drop_statement, create_statements = create_table_sql_list(name, generator, options)
127
+ (execute_ddl(drop_statement) rescue nil) if drop_statement
128
+ create_statements.each{|sql| execute_ddl(sql)}
129
+ end
130
+
154
131
  def create_table_sql_list(name, generator, options={})
155
132
  statements = [create_table_sql(name, generator, options)]
156
133
  drop_seq_statement = nil
@@ -192,6 +169,10 @@ module Sequel
192
169
  end_sql
193
170
  sql
194
171
  end
172
+
173
+ def database_error_classes
174
+ [Fb::Error]
175
+ end
195
176
 
196
177
  def disconnect_connection(c)
197
178
  c.close
@@ -205,9 +186,14 @@ module Sequel
205
186
  seq_name = quote_identifier(name)
206
187
  "ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
207
188
  end
189
+
190
+ def rollback_transaction(conn)
191
+ log_info(TRANSACTION_ROLLBACK)
192
+ conn.rollback
193
+ end
208
194
 
209
195
  def type_literal_generic_string(column)
210
- column[:text] ? :"BLOB SUBTYPE TEXT" : super
196
+ column[:text] ? :"BLOB SUB_TYPE TEXT" : super
211
197
  end
212
198
  end
213
199
 
@@ -209,33 +209,6 @@ module Sequel
209
209
  ts
210
210
  end
211
211
 
212
- # Default transaction method that should work on most JDBC
213
- # databases. Does not use the JDBC transaction methods, uses
214
- # SQL BEGIN/ROLLBACK/COMMIT statements instead.
215
- def transaction(opts={})
216
- synchronize(opts[:server]) do |conn|
217
- return yield(conn) if @transactions.include?(Thread.current)
218
- stmt = conn.createStatement
219
- begin
220
- log_info(begin_transaction_sql)
221
- stmt.execute(begin_transaction_sql)
222
- @transactions << Thread.current
223
- yield(conn)
224
- rescue Exception => e
225
- log_info(rollback_transaction_sql)
226
- stmt.execute(rollback_transaction_sql)
227
- transaction_error(e)
228
- ensure
229
- unless e
230
- log_info(commit_transaction_sql)
231
- stmt.execute(commit_transaction_sql)
232
- end
233
- stmt.close
234
- @transactions.delete(Thread.current)
235
- end
236
- end
237
- end
238
-
239
212
  # The uri for this connection. You can specify the uri
240
213
  # using the :uri, :url, or :database options. You don't
241
214
  # need to worry about this if you use Sequel.connect
@@ -248,6 +221,12 @@ module Sequel
248
221
 
249
222
  private
250
223
 
224
+ # JDBC uses a statement object to execute SQL on the database
225
+ def begin_transaction(conn)
226
+ conn = conn.createStatement
227
+ super
228
+ end
229
+
251
230
  # The JDBC adapter should not need the pool to convert exceptions.
252
231
  def connection_pool_default_options
253
232
  super.merge(:pool_convert_exceptions=>false)
@@ -323,6 +302,12 @@ module Sequel
323
302
  synchronize{|c| metadata_dataset.send(:process_result_set, c.getMetaData.send(*args), &block)}
324
303
  end
325
304
 
305
+ # Close the given statement when removing the transaction
306
+ def remove_transaction(stmt)
307
+ stmt.close if stmt
308
+ super
309
+ end
310
+
326
311
  # Java being java, you need to specify the type of each argument
327
312
  # for the prepared statement, and bind it individually. This
328
313
  # guesses which JDBC method to use, and hopefully JRuby will convert
@@ -372,6 +357,11 @@ module Sequel
372
357
  end
373
358
  ts
374
359
  end
360
+
361
+ # Create a statement object to execute transaction statements.
362
+ def transaction_statement_object(conn)
363
+ conn.createStatement
364
+ end
375
365
  end
376
366
 
377
367
  class Dataset < Sequel::Dataset
@@ -55,6 +55,9 @@ module Sequel
55
55
  class Database < Sequel::Database
56
56
  include Sequel::MySQL::DatabaseMethods
57
57
 
58
+ # Mysql::Error messages that indicate the current connection should be disconnected
59
+ MYSQL_DATABASE_DISCONNECT_ERRORS = /\ACommands out of sync; you can't run this command now\z/
60
+
58
61
  set_adapter_scheme :mysql
59
62
 
60
63
  # Support stored procedures on MySQL
@@ -101,8 +104,7 @@ module Sequel
101
104
 
102
105
  # By default, MySQL 'where id is null' selects the last inserted id
103
106
  conn.query("set SQL_AUTO_IS_NULL=0") unless opts[:auto_is_null]
104
-
105
- conn.query_with_result = false
107
+
106
108
  class << conn
107
109
  attr_accessor :prepared_statements
108
110
  end
@@ -132,29 +134,6 @@ module Sequel
132
134
  def server_version(server=nil)
133
135
  @server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
134
136
  end
135
-
136
- # Support single level transactions on MySQL.
137
- def transaction(opts={})
138
- synchronize(opts[:server]) do |conn|
139
- return yield(conn) if @transactions.include?(Thread.current)
140
- log_info(begin_transaction_sql)
141
- conn.query(begin_transaction_sql)
142
- begin
143
- @transactions << Thread.current
144
- yield(conn)
145
- rescue ::Exception => e
146
- log_info(rollback_transaction_sql)
147
- conn.query(rollback_transaction_sql)
148
- transaction_error(e, Mysql::Error)
149
- ensure
150
- unless e
151
- log_info(commit_transaction_sql)
152
- conn.query(commit_transaction_sql)
153
- end
154
- @transactions.delete(Thread.current)
155
- end
156
- end
157
- end
158
137
 
159
138
  private
160
139
 
@@ -162,33 +141,49 @@ module Sequel
162
141
  # option is :select, yield the result of the query, otherwise
163
142
  # yield the connection if a block is given.
164
143
  def _execute(conn, sql, opts)
165
- log_info(sql)
166
- conn.query(sql)
167
- if opts[:type] == :select
168
- loop do
169
- begin
170
- r = conn.use_result
171
- rescue Mysql::Error
172
- nil
173
- else
174
- begin
175
- yield r
176
- ensure
144
+ begin
145
+ log_info(sql)
146
+ r = conn.query(sql)
147
+ if opts[:type] == :select
148
+ yield r if r
149
+ if conn.respond_to?(:next_result) && conn.next_result
150
+ loop do
177
151
  r.free
152
+ r = nil
153
+ begin
154
+ r = conn.use_result
155
+ rescue Mysql::Error
156
+ break
157
+ end
158
+ yield r
159
+ break unless conn.next_result
178
160
  end
179
161
  end
180
- break unless conn.respond_to?(:next_result) && conn.next_result
162
+ else
163
+ yield conn if block_given?
181
164
  end
182
- else
183
- yield conn if block_given?
165
+ rescue Mysql::Error => e
166
+ raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
167
+ ensure
168
+ r.free if r
184
169
  end
185
170
  end
186
171
 
172
+ # MySQL connections use the query method to execute SQL without a result
173
+ def connection_execute_method
174
+ :query
175
+ end
176
+
187
177
  # MySQL doesn't need the connection pool to convert exceptions.
188
178
  def connection_pool_default_options
189
179
  super.merge(:pool_convert_exceptions=>false)
190
180
  end
191
181
 
182
+ # The MySQL adapter main error class is Mysql::Error
183
+ def database_error_classes
184
+ [Mysql::Error]
185
+ end
186
+
192
187
  # The database name when using the native adapter is always stored in
193
188
  # the :database option.
194
189
  def database_name