sequel 3.0.0 → 3.1.0

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