sequel 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +56 -0
  2. data/README +1 -0
  3. data/Rakefile +1 -1
  4. data/lib/sequel_core.rb +9 -16
  5. data/lib/sequel_core/adapters/ado.rb +6 -15
  6. data/lib/sequel_core/adapters/db2.rb +8 -10
  7. data/lib/sequel_core/adapters/dbi.rb +6 -4
  8. data/lib/sequel_core/adapters/informix.rb +21 -22
  9. data/lib/sequel_core/adapters/jdbc.rb +69 -10
  10. data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
  11. data/lib/sequel_core/adapters/mysql.rb +81 -13
  12. data/lib/sequel_core/adapters/odbc.rb +32 -4
  13. data/lib/sequel_core/adapters/openbase.rb +6 -5
  14. data/lib/sequel_core/adapters/oracle.rb +23 -7
  15. data/lib/sequel_core/adapters/postgres.rb +42 -32
  16. data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
  17. data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
  18. data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
  19. data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
  20. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  21. data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
  22. data/lib/sequel_core/adapters/sqlite.rb +6 -14
  23. data/lib/sequel_core/connection_pool.rb +47 -13
  24. data/lib/sequel_core/database.rb +60 -35
  25. data/lib/sequel_core/database/schema.rb +4 -4
  26. data/lib/sequel_core/dataset.rb +12 -3
  27. data/lib/sequel_core/dataset/convenience.rb +4 -13
  28. data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
  29. data/lib/sequel_core/dataset/sql.rb +144 -85
  30. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  31. data/lib/sequel_core/dataset/unsupported.rb +31 -0
  32. data/lib/sequel_core/exceptions.rb +6 -0
  33. data/lib/sequel_core/schema/generator.rb +4 -3
  34. data/lib/sequel_core/schema/sql.rb +41 -23
  35. data/lib/sequel_core/sql.rb +29 -1
  36. data/lib/sequel_model/associations.rb +1 -1
  37. data/lib/sequel_model/record.rb +31 -28
  38. data/spec/adapters/mysql_spec.rb +37 -4
  39. data/spec/adapters/oracle_spec.rb +26 -4
  40. data/spec/adapters/sqlite_spec.rb +7 -0
  41. data/spec/integration/prepared_statement_test.rb +24 -0
  42. data/spec/integration/schema_test.rb +1 -1
  43. data/spec/sequel_core/connection_pool_spec.rb +49 -2
  44. data/spec/sequel_core/core_sql_spec.rb +9 -2
  45. data/spec/sequel_core/database_spec.rb +64 -14
  46. data/spec/sequel_core/dataset_spec.rb +105 -7
  47. data/spec/sequel_core/schema_spec.rb +40 -12
  48. data/spec/sequel_core/spec_helper.rb +1 -0
  49. data/spec/sequel_model/spec_helper.rb +1 -0
  50. metadata +6 -3
@@ -0,0 +1,75 @@
1
+ module Sequel
2
+ class Dataset
3
+ module StoredProcedureMethods
4
+ SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
5
+ SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
6
+
7
+ # The name of the stored procedure to call
8
+ attr_accessor :sproc_name
9
+
10
+ # Call the prepared statement
11
+ def call(*args, &block)
12
+ @sproc_args = args
13
+ case @sproc_type
14
+ when :select, :all
15
+ all(&block)
16
+ when :first
17
+ first
18
+ when :insert
19
+ insert
20
+ when :update
21
+ update
22
+ when :delete
23
+ delete
24
+ end
25
+ end
26
+
27
+ # Programmer friendly string showing this is a stored procedure,
28
+ # showing the name of the procedure.
29
+ def inspect
30
+ "<#{self.class.name}/StoredProcedure name=#{@sproc_name}>"
31
+ end
32
+
33
+ # Set the type of the sproc and override the corresponding _sql
34
+ # method to return the empty string (since the result will be
35
+ # ignored anyway).
36
+ def sproc_type=(type)
37
+ @sproc_type = type
38
+ meta_def("#{sql_query_type}_sql"){|*a| ''}
39
+ end
40
+
41
+ private
42
+
43
+ # The type of query (:select, :insert, :delete, :update).
44
+ def sql_query_type
45
+ SQL_QUERY_TYPE[@sproc_type]
46
+ end
47
+ end
48
+
49
+ module StoredProcedures
50
+ # For the given type (:select, :first, :insert, :update, or :delete),
51
+ # run the database stored procedure with the given name with the given
52
+ # arguments.
53
+ def call_sproc(type, name, *args)
54
+ prepare_sproc(type, name).call(*args)
55
+ end
56
+
57
+ # Transform this dataset into a stored procedure that you can call
58
+ # multiple times with new arguments.
59
+ def prepare_sproc(type, name)
60
+ sp = clone
61
+ prepare_extend_sproc(sp)
62
+ sp.sproc_type = type
63
+ sp.sproc_name = name
64
+ sp
65
+ end
66
+
67
+ private
68
+
69
+ # Extend the dataset with the stored procedure methods.
70
+ def prepare_extend_sproc(ds)
71
+ ds.extend(StoredProcedureMethods)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,31 @@
1
+ class Sequel::Dataset
2
+ # This module should be included in the dataset class for all databases that
3
+ # don't support INTERSECT or EXCEPT.
4
+ module UnsupportedIntersectExcept
5
+ # Raise an Error if EXCEPT is used
6
+ def except(ds, all=false)
7
+ raise(Sequel::Error, "EXCEPT not supported")
8
+ end
9
+
10
+ # Raise an Error if INTERSECT is used
11
+ def intersect(ds, all=false)
12
+ raise(Sequel::Error, "INTERSECT not supported")
13
+ end
14
+ end
15
+
16
+ # This module should be included in the dataset class for all databases that
17
+ # don't support INTERSECT ALL or EXCEPT ALL.
18
+ module UnsupportedIntersectExceptAll
19
+ # Raise an Error if EXCEPT is used
20
+ def except(ds, all=false)
21
+ raise(Sequel::Error, "EXCEPT ALL not supported") if all
22
+ super(ds)
23
+ end
24
+
25
+ # Raise an Error if INTERSECT is used
26
+ def intersect(ds, all=false)
27
+ raise(Sequel::Error, "INTERSECT ALL not supported") if all
28
+ super(ds)
29
+ end
30
+ end
31
+ end
@@ -31,4 +31,10 @@ module Sequel
31
31
  # Generic error raised by the database adapters, indicating a
32
32
  # problem originating from the database server.
33
33
  class DatabaseError < Error; end
34
+
35
+ # Error that should be raised by adapters when they determine that the connection
36
+ # to the database has been lost. Instructs the connection pool code to
37
+ # remove that connection from the pool so that other connections can be acquired
38
+ # automatically.
39
+ class DatabaseDisconnectError < DatabaseError; end
34
40
  end
@@ -127,9 +127,10 @@ module Sequel
127
127
  # optional middle argument denotes the type.
128
128
  #
129
129
  # Examples:
130
- # primary_key(:id) primary_key(:name, :text)
130
+ # primary_key(:id)
131
131
  # primary_key(:zip_code, :null => false)
132
132
  # primary_key([:street_number, :house_number])
133
+ # primary_key(:id, :string, :auto_increment => false)
133
134
  def primary_key(name, *args)
134
135
  return composite_primary_key(name, *args) if name.is_a?(Array)
135
136
  @primary_key = @db.serial_primary_key_options.merge({:name => name})
@@ -276,8 +277,8 @@ module Sequel
276
277
  end
277
278
 
278
279
  # Modify a column's type in the DDL for the table.
279
- def set_column_type(name, type)
280
- @operations << {:op => :set_column_type, :name => name, :type => type}
280
+ def set_column_type(name, type, opts={})
281
+ @operations << {:op => :set_column_type, :name => name, :type => type}.merge(opts)
281
282
  end
282
283
 
283
284
  # Modify a column's NOT NULL constraint.
@@ -29,7 +29,7 @@ module Sequel
29
29
  when :rename_column
30
30
  "RENAME COLUMN #{quoted_name} TO #{quote_identifier(op[:new_name])}"
31
31
  when :set_column_type
32
- "ALTER COLUMN #{quoted_name} TYPE #{op[:type]}"
32
+ "ALTER COLUMN #{quoted_name} TYPE #{type_literal(op)}"
33
33
  when :set_column_default
34
34
  "ALTER COLUMN #{quoted_name} SET DEFAULT #{literal(op[:default])}"
35
35
  when :set_column_null
@@ -187,9 +187,9 @@ module Sequel
187
187
  end
188
188
  end
189
189
 
190
+ # Proxy the quote_schema_table method to the dataset
190
191
  def quote_schema_table(table)
191
- schema, table = schema_and_table(table)
192
- "#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
192
+ schema_utility_dataset.quote_schema_table(table)
193
193
  end
194
194
 
195
195
  # Proxy the quote_identifier method to the dataset, used for quoting tables and columns.
@@ -202,7 +202,7 @@ module Sequel
202
202
  "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_schema_table(new_name)}"
203
203
  end
204
204
 
205
- # Parse the schema from the database using the SQL standard INFORMATION_SCHEMA.
205
+ # Parse the schema from the database.
206
206
  # If the table_name is not given, returns the schema for all tables as a hash.
207
207
  # If the table_name is given, returns the schema for a single table as an
208
208
  # array with all members being arrays of length 2. Available options are:
@@ -212,11 +212,17 @@ module Sequel
212
212
  # unless you are sure that schema has not been called before with a
213
213
  # table_name, otherwise you may only getting the schemas for tables
214
214
  # that have been requested explicitly.
215
- def schema(table_name = nil, opts={})
216
- table_name = table_name.to_sym if table_name
215
+ # * :schema - An explicit schema to use. It may also be implicitly provided
216
+ # via the table name.
217
+ def schema(table = nil, opts={})
218
+ if table
219
+ sch, table_name = schema_and_table(table)
220
+ quoted_name = quote_schema_table(table)
221
+ end
222
+ opts = opts.merge(:schema=>sch) if sch && !opts.include?(:schema)
217
223
  if opts[:reload] && @schemas
218
224
  if table_name
219
- @schemas.delete(table_name)
225
+ @schemas.delete(quoted_name)
220
226
  else
221
227
  @schemas = nil
222
228
  end
@@ -224,26 +230,31 @@ module Sequel
224
230
 
225
231
  if @schemas
226
232
  if table_name
227
- return @schemas[table_name] if @schemas[table_name]
233
+ return @schemas[quoted_name] if @schemas[quoted_name]
228
234
  else
229
235
  return @schemas
230
236
  end
231
237
  end
238
+
239
+ @schemas ||= Hash.new do |h,k|
240
+ quote_name = quote_schema_table(k)
241
+ h[quote_name] if h.include?(quote_name)
242
+ end
232
243
 
233
244
  if table_name
234
- @schemas ||= {}
235
245
  if respond_to?(:schema_parse_table, true)
236
- @schemas[table_name] ||= schema_parse_table(table_name, opts)
246
+ @schemas[quoted_name] = schema_parse_table(table_name, opts)
237
247
  else
238
248
  raise Error, 'schema parsing is not implemented on this database'
239
249
  end
240
250
  else
241
251
  if respond_to?(:schema_parse_tables, true)
242
- @schemas = schema_parse_tables(opts)
252
+ @schemas.merge!(schema_parse_tables(opts))
243
253
  elsif respond_to?(:schema_parse_table, true) and respond_to?(:tables, true)
244
- tables.each{|t| schema(t, opts)}
254
+ tables.each{|t| @schemas[quote_identifier(t)] = schema_parse_table(t.to_s, opts)}
245
255
  @schemas
246
256
  else
257
+ @schemas = nil
247
258
  raise Error, 'schema parsing is not implemented on this database'
248
259
  end
249
260
  end
@@ -256,30 +267,37 @@ module Sequel
256
267
 
257
268
  private
258
269
 
270
+ # Remove the cached schema for the given schema name
271
+ def remove_cached_schema(table)
272
+ @schemas.delete(quote_schema_table(table)) if @schemas
273
+ end
274
+
259
275
  # Match the database's column type to a ruby type via a
260
276
  # regular expression. The following ruby types are supported:
261
277
  # integer, string, date, datetime, boolean, and float.
262
278
  def schema_column_type(db_type)
263
279
  case db_type
264
- when /\Atinyint/
280
+ when /\Atinyint/io
265
281
  Sequel.convert_tinyint_to_bool ? :boolean : :integer
266
- when /\A(int(eger)?|bigint|smallint)/
267
- :integer
268
- when /\A(character( varying)?|varchar|text)/
282
+ when /\Ainterval\z/io
283
+ :interval
284
+ when /\A(character( varying)?|varchar|text)/io
269
285
  :string
270
- when /\Adate\z/
286
+ when /\A(int(eger)?|bigint|smallint)/io
287
+ :integer
288
+ when /\Adate\z/io
271
289
  :date
272
- when /\A(datetime|timestamp( with(out)? time zone)?)\z/
290
+ when /\A(datetime|timestamp( with(out)? time zone)?)\z/io
273
291
  :datetime
274
- when /\Atime( with(out)? time zone)?\z/
292
+ when /\Atime( with(out)? time zone)?\z/io
275
293
  :time
276
- when "boolean"
294
+ when /\Aboolean\z/io
277
295
  :boolean
278
- when /\A(real|float|double( precision)?)\z/
296
+ when /\A(real|float|double( precision)?)\z/io
279
297
  :float
280
- when /\A(numeric(\(\d+,\d+\))?|decimal|money)\z/
298
+ when /\A(numeric(\(\d+,\d+\))?|decimal|money)\z/io
281
299
  :decimal
282
- when "bytea"
300
+ when /\Abytea\z/io
283
301
  :blob
284
302
  end
285
303
  end
@@ -685,6 +685,34 @@ module Sequel
685
685
  end
686
686
  end
687
687
 
688
+ # Represents a literal string with placeholders and arguments.
689
+ # This is necessary to ensure delayed literalization of the arguments
690
+ # required for the prepared statement support
691
+ class PlaceholderLiteralString < SpecificExpression
692
+ # The arguments that will be subsituted into the placeholders.
693
+ attr_reader :args
694
+
695
+ # The literal string containing placeholders
696
+ attr_reader :str
697
+
698
+ # Whether to surround the expression with parantheses
699
+ attr_reader :parens
700
+
701
+ # Create an object with the given conditions and
702
+ # default value.
703
+ def initialize(str, args, parens=false)
704
+ @str = str
705
+ @args = args
706
+ @parens = parens
707
+ end
708
+
709
+ # Delegate the creation of the resulting SQL to the given dataset,
710
+ # since it may be database dependent.
711
+ def to_s(ds)
712
+ ds.placeholder_literal_string_sql(self)
713
+ end
714
+ end
715
+
688
716
  # Subclass of ComplexExpression where the expression results
689
717
  # in a numeric value in SQL.
690
718
  class NumericExpression < ComplexExpression
@@ -831,7 +859,7 @@ module Sequel
831
859
  # LiteralString is used to represent literal SQL expressions. A
832
860
  # LiteralString is copied verbatim into an SQL statement. Instances of
833
861
  # LiteralString can be created by calling String#lit.
834
- # LiteralStrings can use all of the SQL::ColumnMethods and the
862
+ # LiteralStrings can also use all of the SQL::OrderMethods and the
835
863
  # SQL::ComplexExpressionMethods.
836
864
  class LiteralString < ::String
837
865
  include SQL::OrderMethods
@@ -394,7 +394,7 @@ module Sequel::Model::Associations
394
394
  return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
395
395
  return if o and run_association_callbacks(opts, :before_add, o) == false
396
396
  send(opts._setter_method, o)
397
- @associations[name] = o
397
+ associations[name] = o
398
398
  remove_reciprocal_object(opts, old_val) if old_val
399
399
  add_reciprocal_object(opts, o) if o
400
400
  run_association_callbacks(opts, :after_add, o) if o
@@ -4,15 +4,6 @@ module Sequel
4
4
  # to be called automatically via set.
5
5
  RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
6
6
 
7
- # The current cached associations. A hash with the keys being the
8
- # association name symbols and the values being the associated object
9
- # or nil (many_to_one), or the array of associated objects (*_to_many).
10
- attr_reader :associations
11
-
12
- # The columns that have been updated. This isn't completely accurate,
13
- # see Model#[]=.
14
- attr_reader :changed_columns
15
-
16
7
  # The hash of attribute values. Keys are symbols with the names of the
17
8
  # underlying database columns.
18
9
  attr_reader :values
@@ -32,9 +23,7 @@ module Sequel
32
23
  # string keys will work if from_db is false.
33
24
  # * from_db - should only be set by Model.load, forget it
34
25
  # exists.
35
- def initialize(values = {}, from_db = false, &block)
36
- @associations = {}
37
- @changed_columns = []
26
+ def initialize(values = {}, from_db = false)
38
27
  if from_db
39
28
  @new = false
40
29
  @values = values
@@ -42,8 +31,8 @@ module Sequel
42
31
  @values = {}
43
32
  @new = true
44
33
  set(values)
45
- @changed_columns.clear
46
- yield self if block
34
+ changed_columns.clear
35
+ yield self if block_given?
47
36
  end
48
37
  after_initialize
49
38
  end
@@ -61,7 +50,7 @@ module Sequel
61
50
  # If the column isn't in @values, we can't assume it is
62
51
  # NULL in the database, so assume it has changed.
63
52
  if new? || !@values.include?(column) || value != @values[column]
64
- @changed_columns << column unless @changed_columns.include?(column)
53
+ changed_columns << column unless changed_columns.include?(column)
65
54
  @values[column] = typecast_value(column, value)
66
55
  end
67
56
  end
@@ -84,6 +73,19 @@ module Sequel
84
73
  # self.class.
85
74
  alias_method :model, :class
86
75
 
76
+ # The current cached associations. A hash with the keys being the
77
+ # association name symbols and the values being the associated object
78
+ # or nil (many_to_one), or the array of associated objects (*_to_many).
79
+ def associations
80
+ @associations ||= {}
81
+ end
82
+
83
+ # The columns that have been updated. This isn't completely accurate,
84
+ # see Model#[]=.
85
+ def changed_columns
86
+ @changed_columns ||= []
87
+ end
88
+
87
89
  # Deletes and returns self. Does not run destroy hooks.
88
90
  # Look into using destroy instead.
89
91
  def delete
@@ -171,7 +173,8 @@ module Sequel
171
173
  # exists in the database.
172
174
  def refresh
173
175
  @values = this.first || raise(Error, "Record not found")
174
- @associations.clear
176
+ changed_columns.clear
177
+ associations.clear
175
178
  self
176
179
  end
177
180
  alias_method :reload, :refresh
@@ -220,12 +223,12 @@ module Sequel
220
223
  else
221
224
  return save_failure(:update) if before_update == false
222
225
  if columns.empty?
223
- vals = opts[:changed] ? @values.reject{|k,v| !@changed_columns.include?(k)} : @values
226
+ vals = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values
224
227
  this.update(vals)
225
- @changed_columns = []
228
+ changed_columns.clear
226
229
  else # update only the specified columns
227
- this.update(@values.reject {|k, v| !columns.include?(k)})
228
- @changed_columns.reject! {|c| columns.include?(c)}
230
+ this.update(@values.reject{|k, v| !columns.include?(k)})
231
+ changed_columns.reject!{|c| columns.include?(c)}
229
232
  end
230
233
  after_update
231
234
  end
@@ -237,7 +240,7 @@ module Sequel
237
240
  # chanaged. If no columns have been changed, returns nil. If unable to
238
241
  # save, returns false unless raise_on_save_failure is true.
239
242
  def save_changes
240
- save(:changed=>true) || false unless @changed_columns.empty?
243
+ save(:changed=>true) || false unless changed_columns.empty?
241
244
  end
242
245
 
243
246
  # Updates the instance with the supplied values with support for virtual
@@ -357,7 +360,7 @@ module Sequel
357
360
  raise(Sequel::Error, 'associated object does not have a primary key') if opts.need_associated_primary_key? && !o.pk
358
361
  return if run_association_callbacks(opts, :before_add, o) == false
359
362
  send(opts._add_method, o)
360
- @associations[opts[:name]].push(o) if @associations.include?(opts[:name])
363
+ associations[opts[:name]].push(o) if associations.include?(opts[:name])
361
364
  add_reciprocal_object(opts, o)
362
365
  run_association_callbacks(opts, :after_add, o)
363
366
  o
@@ -378,8 +381,8 @@ module Sequel
378
381
  # Load the associated objects using the dataset
379
382
  def load_associated_objects(opts, reload=false)
380
383
  name = opts[:name]
381
- if @associations.include?(name) and !reload
382
- @associations[name]
384
+ if associations.include?(name) and !reload
385
+ associations[name]
383
386
  else
384
387
  objs = if opts.returns_array?
385
388
  send(opts.dataset_method).all
@@ -393,7 +396,7 @@ module Sequel
393
396
  run_association_callbacks(opts, :after_load, objs)
394
397
  # Only one_to_many associations should set the reciprocal object
395
398
  objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
396
- @associations[name] = objs
399
+ associations[name] = objs
397
400
  end
398
401
  end
399
402
 
@@ -401,8 +404,8 @@ module Sequel
401
404
  def remove_all_associated_objects(opts)
402
405
  raise(Sequel::Error, 'model object does not have a primary key') unless pk
403
406
  send(opts._remove_all_method)
404
- ret = @associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if @associations.include?(opts[:name])
405
- @associations[opts[:name]] = []
407
+ ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
408
+ associations[opts[:name]] = []
406
409
  ret
407
410
  end
408
411
 
@@ -412,7 +415,7 @@ module Sequel
412
415
  raise(Sequel::Error, 'associated object does not have a primary key') if opts.need_associated_primary_key? && !o.pk
413
416
  return if run_association_callbacks(opts, :before_remove, o) == false
414
417
  send(opts._remove_method, o)
415
- @associations[opts[:name]].delete_if{|x| o === x} if @associations.include?(opts[:name])
418
+ associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
416
419
  remove_reciprocal_object(opts, o)
417
420
  run_association_callbacks(opts, :after_remove, o)
418
421
  o