sequel 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +6 -28
  3. data/bin/sequel +7 -2
  4. data/doc/release_notes/3.9.0.txt +233 -0
  5. data/lib/sequel/adapters/ado.rb +4 -8
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/dbi.rb +3 -3
  8. data/lib/sequel/adapters/do.rb +7 -13
  9. data/lib/sequel/adapters/jdbc.rb +10 -16
  10. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  11. data/lib/sequel/adapters/mysql.rb +10 -23
  12. data/lib/sequel/adapters/odbc.rb +6 -10
  13. data/lib/sequel/adapters/postgres.rb +0 -5
  14. data/lib/sequel/adapters/shared/mssql.rb +17 -9
  15. data/lib/sequel/adapters/shared/mysql.rb +16 -7
  16. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  17. data/lib/sequel/adapters/sqlite.rb +2 -1
  18. data/lib/sequel/connection_pool.rb +67 -349
  19. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  20. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  21. data/lib/sequel/connection_pool/single.rb +29 -0
  22. data/lib/sequel/connection_pool/threaded.rb +150 -0
  23. data/lib/sequel/core.rb +46 -15
  24. data/lib/sequel/database.rb +11 -9
  25. data/lib/sequel/dataset/convenience.rb +23 -0
  26. data/lib/sequel/dataset/graph.rb +2 -2
  27. data/lib/sequel/dataset/query.rb +9 -5
  28. data/lib/sequel/dataset/sql.rb +87 -12
  29. data/lib/sequel/extensions/inflector.rb +8 -1
  30. data/lib/sequel/extensions/schema_dumper.rb +3 -4
  31. data/lib/sequel/model/associations.rb +5 -43
  32. data/lib/sequel/model/base.rb +9 -2
  33. data/lib/sequel/model/default_inflections.rb +1 -1
  34. data/lib/sequel/model/exceptions.rb +11 -1
  35. data/lib/sequel/model/inflections.rb +8 -1
  36. data/lib/sequel/model/plugins.rb +2 -12
  37. data/lib/sequel/plugins/active_model.rb +5 -0
  38. data/lib/sequel/plugins/association_dependencies.rb +1 -1
  39. data/lib/sequel/plugins/many_through_many.rb +1 -1
  40. data/lib/sequel/plugins/optimistic_locking.rb +65 -0
  41. data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
  42. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  43. data/lib/sequel/sql.rb +2 -2
  44. data/lib/sequel/timezones.rb +2 -2
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/mssql_spec.rb +19 -0
  47. data/spec/adapters/mysql_spec.rb +4 -0
  48. data/spec/adapters/postgres_spec.rb +180 -0
  49. data/spec/adapters/spec_helper.rb +15 -1
  50. data/spec/core/connection_pool_spec.rb +119 -78
  51. data/spec/core/database_spec.rb +41 -50
  52. data/spec/core/dataset_spec.rb +115 -4
  53. data/spec/extensions/active_model_spec.rb +40 -34
  54. data/spec/extensions/boolean_readers_spec.rb +1 -1
  55. data/spec/extensions/migration_spec.rb +43 -38
  56. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  57. data/spec/extensions/schema_dumper_spec.rb +4 -4
  58. data/spec/extensions/single_table_inheritance_spec.rb +19 -11
  59. data/spec/integration/dataset_test.rb +44 -1
  60. data/spec/integration/plugin_test.rb +39 -0
  61. data/spec/integration/prepared_statement_test.rb +58 -7
  62. data/spec/integration/spec_helper.rb +4 -0
  63. data/spec/model/eager_loading_spec.rb +24 -0
  64. data/spec/model/validations_spec.rb +5 -1
  65. metadata +114 -106
data/lib/sequel/core.rb CHANGED
@@ -17,18 +17,6 @@
17
17
  #
18
18
  # Sequel.sqlite('blog.db'){|db| puts db[:users].count}
19
19
  #
20
- # Sequel converts two digit years in Dates and DateTimes by default,
21
- # so 01/02/03 is interpreted at January 2nd, 2003, and 12/13/99 is interpreted
22
- # as December 13, 1999. You can override this to treat those dates as
23
- # January 2nd, 0003 and December 13, 0099, respectively, by setting:
24
- #
25
- # Sequel.convert_two_digit_years = false
26
- #
27
- # Sequel can use either Time or DateTime for times returned from the
28
- # database. It defaults to Time. To change it to DateTime, use:
29
- #
30
- # Sequel.datetime_class = DateTime
31
- #
32
20
  # Sequel doesn't pay much attention to timezones by default, but you can set it
33
21
  # handle timezones if you want. There are three separate timezone settings:
34
22
  #
@@ -62,9 +50,42 @@ module Sequel
62
50
  @convert_two_digit_years = true
63
51
  @datetime_class = Time
64
52
  @virtual_row_instance_eval = true
53
+ @require_thread = nil
54
+
55
+ # Mutex used to protect file loading
56
+ @require_mutex = Mutex.new
65
57
 
66
58
  class << self
67
- attr_accessor :convert_two_digit_years, :datetime_class, :virtual_row_instance_eval
59
+ # Sequel converts two digit years in Dates and DateTimes by default,
60
+ # so 01/02/03 is interpreted at January 2nd, 2003, and 12/13/99 is interpreted
61
+ # as December 13, 1999. You can override this to treat those dates as
62
+ # January 2nd, 0003 and December 13, 0099, respectively, by setting this to false.
63
+ attr_accessor :convert_two_digit_years
64
+
65
+ # Sequel can use either Time or DateTime for times returned from the
66
+ # database. It defaults to Time. To change it to DateTime, set this to DateTime.
67
+ attr_accessor :datetime_class
68
+
69
+ attr_accessor :virtual_row_instance_eval
70
+
71
+ # Alias to the standard version of require
72
+ alias k_require require
73
+
74
+ private
75
+
76
+ # Make thread safe requiring reentrant to prevent deadlocks.
77
+ def check_requiring_thread
78
+ t = Thread.current
79
+ return(yield) if @require_thread == t
80
+ @require_mutex.synchronize do
81
+ begin
82
+ @require_thread = t
83
+ yield
84
+ ensure
85
+ @require_thread = nil
86
+ end
87
+ end
88
+ end
68
89
  end
69
90
 
70
91
  # Returns true if the passed object could be a specifier of conditions, false otherwise.
@@ -117,7 +138,7 @@ module Sequel
117
138
  # Sequel.extension(:schema_dumper)
118
139
  # Sequel.extension(:pagination, :query)
119
140
  def self.extension(*extensions)
120
- require(extensions, 'extensions')
141
+ extensions.each{|e| tsk_require "sequel/extensions/#{e}"}
121
142
  end
122
143
 
123
144
  # Set the method to call on identifiers going into the database. This affects
@@ -158,7 +179,7 @@ module Sequel
158
179
  def self.quote_identifiers=(value)
159
180
  Database.quote_identifiers = value
160
181
  end
161
-
182
+
162
183
  # Require all given files which should be in the same or a subdirectory of
163
184
  # this file. If a subdir is given, assume all files are in that subdir.
164
185
  def self.require(files, subdir=nil)
@@ -207,6 +228,16 @@ module Sequel
207
228
  end
208
229
  end
209
230
 
231
+ # Same as Sequel.require, but wrapped in a mutex in order to be thread safe.
232
+ def self.ts_require(*args)
233
+ check_requiring_thread{require(*args)}
234
+ end
235
+
236
+ # Same as Kernel.require, but wrapped in a mutex in order to be thread safe.
237
+ def self.tsk_require(*args)
238
+ check_requiring_thread{k_require(*args)}
239
+ end
240
+
210
241
  # If the supplied block takes a single argument,
211
242
  # yield a new SQL::VirtualRow instance to the block
212
243
  # argument. Otherwise, evaluate the block in the context of a new
@@ -80,20 +80,22 @@ module Sequel
80
80
  # is given, it is used as the connection_proc for the ConnectionPool.
81
81
  def initialize(opts = {}, &block)
82
82
  @opts ||= opts
83
+ @opts = connection_pool_default_options.merge(@opts)
84
+ @loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
85
+ @opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
86
+ block ||= proc{|server| connect(server)}
87
+ @opts[:servers] = {} if @opts[:servers].is_a?(String)
83
88
 
84
- @single_threaded = opts.include?(:single_threaded) ? typecast_value_boolean(opts[:single_threaded]) : @@single_threaded
89
+ @opts[:single_threaded] = @single_threaded = @opts.include?(:single_threaded) ? typecast_value_boolean(@opts[:single_threaded]) : @@single_threaded
85
90
  @schemas = {}
86
- @default_schema = opts.include?(:default_schema) ? opts[:default_schema] : default_schema_default
91
+ @default_schema = opts.include?(:default_schema) ? @opts[:default_schema] : default_schema_default
87
92
  @prepared_statements = {}
88
93
  @transactions = []
89
94
  @identifier_input_method = nil
90
95
  @identifier_output_method = nil
91
96
  @quote_identifiers = nil
92
- @pool = (@single_threaded ? SingleThreadedPool : ConnectionPool).new(connection_pool_default_options.merge(opts), &block)
93
- @pool.connection_proc = proc{|server| connect(server)} unless block
94
- @pool.disconnection_proc = proc{|conn| disconnect_connection(conn)} unless opts[:disconnection_proc]
97
+ @pool = ConnectionPool.get_pool(@opts, &block)
95
98
 
96
- @loggers = Array(opts[:logger]) + Array(opts[:loggers])
97
99
  ::Sequel::DATABASES.push(self)
98
100
  end
99
101
 
@@ -108,7 +110,7 @@ module Sequel
108
110
  unless klass = ADAPTER_MAP[scheme]
109
111
  # attempt to load the adapter file
110
112
  begin
111
- Sequel.require "adapters/#{scheme}"
113
+ Sequel.tsk_require "sequel/adapters/#{scheme}"
112
114
  rescue LoadError => e
113
115
  raise Sequel.convert_exception_class(e, AdapterNotFound)
114
116
  end
@@ -139,7 +141,7 @@ module Sequel
139
141
  scheme = :dbi if scheme =~ /\Adbi-/
140
142
  c = adapter_class(scheme)
141
143
  uri_options = c.send(:uri_to_options, uri)
142
- uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v} unless uri.query.to_s.strip.empty?
144
+ uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
143
145
  uri_options.entries.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
144
146
  opts = uri_options.merge(opts)
145
147
  end
@@ -281,7 +283,7 @@ module Sequel
281
283
  end
282
284
 
283
285
  # Connects to the database. This method should be overridden by descendants.
284
- def connect
286
+ def connect(server)
285
287
  raise NotImplementedError, "#connect should be overridden by adapters"
286
288
  end
287
289
 
@@ -274,6 +274,29 @@ module Sequel
274
274
  end
275
275
  end
276
276
 
277
+ # Creates a unique table alias that hasn't already been used in the dataset.
278
+ # table_alias can be any type of object accepted by alias_symbol.
279
+ # The symbol returned will be the implicit alias in the argument,
280
+ # possibly appended with "_N" if the implicit alias has already been
281
+ # used, where N is an integer starting at 0 and increasing until an
282
+ # unused one is found.
283
+ def unused_table_alias(table_alias)
284
+ table_alias = alias_symbol(table_alias)
285
+ used_aliases = []
286
+ used_aliases += opts[:from].map{|t| alias_symbol(t)} if opts[:from]
287
+ used_aliases += opts[:join].map{|j| j.table_alias ? alias_alias_symbol(j.table_alias) : alias_symbol(j.table)} if opts[:join]
288
+ if used_aliases.include?(table_alias)
289
+ i = 0
290
+ loop do
291
+ ta = :"#{table_alias}_#{i}"
292
+ return ta unless used_aliases.include?(ta)
293
+ i += 1
294
+ end
295
+ else
296
+ table_alias
297
+ end
298
+ end
299
+
277
300
  private
278
301
 
279
302
  # Return a plain symbol given a potentially qualified or aliased symbol,
@@ -5,7 +5,7 @@ module Sequel
5
5
  # #set_graph_aliases.
6
6
  def add_graph_aliases(graph_aliases)
7
7
  ds = select_more(*graph_alias_columns(graph_aliases))
8
- ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || ds.opts[:graph][:column_aliases] || {}).merge(graph_aliases)
8
+ ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || (ds.opts[:graph][:column_aliases] rescue {}) || {}).merge(graph_aliases)
9
9
  ds
10
10
  end
11
11
 
@@ -102,7 +102,7 @@ module Sequel
102
102
 
103
103
  # Setup the initial graph data structure if it doesn't exist
104
104
  unless graph = opts[:graph]
105
- master = ds.first_source_alias
105
+ master = alias_symbol(ds.first_source_alias)
106
106
  raise_alias_error.call if master == table_alias
107
107
  # Master hash storing all .graph related information
108
108
  graph = opts[:graph] = {}
@@ -221,12 +221,16 @@ module Sequel
221
221
  o = l.first
222
222
  l = l.last - l.first + (l.exclude_end? ? 0 : 1)
223
223
  end
224
- l = l.to_i
225
- raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
224
+ l = l.to_i if l.is_a?(String) && !l.is_a?(LiteralString)
225
+ if l.is_a?(Integer)
226
+ raise(Error, 'Limits must be greater than or equal to 1') unless l >= 1
227
+ end
226
228
  opts = {:limit => l}
227
229
  if o
228
- o = o.to_i
229
- raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
230
+ o = o.to_i if o.is_a?(String) && !o.is_a?(LiteralString)
231
+ if o.is_a?(Integer)
232
+ raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
233
+ end
230
234
  opts[:offset] = o
231
235
  end
232
236
  clone(opts)
@@ -285,7 +289,7 @@ module Sequel
285
289
  #
286
290
  # dataset.select(:a) # SELECT a FROM items
287
291
  # dataset.select(:a, :b) # SELECT a, b FROM items
288
- # dataset.select{|o| o.a, o.sum(:b)} # SELECT a, sum(b) FROM items
292
+ # dataset.select{|o| [o.a, o.sum(:b)]} # SELECT a, sum(b) FROM items
289
293
  def select(*columns, &block)
290
294
  columns += Array(Sequel.virtual_row(&block)) if block
291
295
  m = []
@@ -91,11 +91,42 @@ module Sequel
91
91
  end
92
92
  when :IN, :"NOT IN"
93
93
  cols = args.at(0)
94
- if !supports_multiple_column_in? && cols.is_a?(Array)
95
- expr = SQL::BooleanExpression.new(:OR, *args.at(1).to_a.map{|vals| SQL::BooleanExpression.from_value_pairs(cols.zip(vals).map{|col, val| [col, val]})})
96
- literal(op == :IN ? expr : ~expr)
94
+ vals = args.at(1)
95
+ col_array = true if cols.is_a?(Array) || cols.is_a?(SQL::SQLArray)
96
+ if vals.is_a?(Array) || vals.is_a?(SQL::SQLArray)
97
+ val_array = true
98
+ empty_val_array = vals.to_a == []
99
+ end
100
+ if col_array
101
+ if empty_val_array
102
+ if op == :IN
103
+ literal(SQL::BooleanExpression.from_value_pairs(cols.to_a.map{|x| [x, x]}, :AND, true))
104
+ else
105
+ literal(1=>1)
106
+ end
107
+ elsif !supports_multiple_column_in?
108
+ if val_array
109
+ expr = SQL::BooleanExpression.new(:OR, *vals.to_a.map{|vs| SQL::BooleanExpression.from_value_pairs(cols.to_a.zip(vs).map{|c, v| [c, v]})})
110
+ literal(op == :IN ? expr : ~expr)
111
+ else
112
+ old_vals = vals
113
+ vals = vals.to_a
114
+ val_cols = old_vals.columns
115
+ complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
116
+ end
117
+ else
118
+ "(#{literal(cols)} #{op} #{literal(vals)})"
119
+ end
97
120
  else
98
- "(#{literal(cols)} #{op} #{literal(args.at(1))})"
121
+ if empty_val_array
122
+ if op == :IN
123
+ literal(SQL::BooleanExpression.from_value_pairs([[cols, cols]], :AND, true))
124
+ else
125
+ literal(1=>1)
126
+ end
127
+ else
128
+ "(#{literal(cols)} #{op} #{literal(vals)})"
129
+ end
99
130
  end
100
131
  when *TWO_ARITY_OPERATORS
101
132
  "(#{literal(args.at(0))} #{op} #{literal(args.at(1))})"
@@ -288,13 +319,17 @@ module Sequel
288
319
  return join_table(type, table, h, options)
289
320
  end
290
321
 
291
- if [Symbol, String].any?{|c| options.is_a?(c)}
322
+ case options
323
+ when Hash
324
+ table_alias = options[:table_alias]
325
+ last_alias = options[:implicit_qualifier]
326
+ when Symbol, String, SQL::Identifier
292
327
  table_alias = options
293
328
  last_alias = nil
294
329
  else
295
- table_alias = options[:table_alias]
296
- last_alias = options[:implicit_qualifier]
330
+ raise Error, "invalid options format for join_table: #{options.inspect}"
297
331
  end
332
+
298
333
  if Dataset === table
299
334
  if table_alias.nil?
300
335
  table_alias_num = (@opts[:num_dataset_sources] || 0) + 1
@@ -360,7 +395,7 @@ module Sequel
360
395
  when BigDecimal
361
396
  literal_big_decimal(v)
362
397
  when NilClass
363
- NULL
398
+ literal_nil
364
399
  when TrueClass
365
400
  literal_true
366
401
  when FalseClass
@@ -630,6 +665,41 @@ module Sequel
630
665
  "TRUNCATE TABLE #{table}"
631
666
  end
632
667
 
668
+ # Returns an appropriate symbol for the alias represented by s.
669
+ def alias_alias_symbol(s)
670
+ case s
671
+ when Symbol
672
+ s
673
+ when String
674
+ s.to_sym
675
+ when SQL::Identifier
676
+ s.value.to_s.to_sym
677
+ else
678
+ raise Error, "Invalid alias for alias_alias_symbol: #{s.inspect}"
679
+ end
680
+ end
681
+
682
+ # Returns an appropriate alias symbol for the given object, which can be
683
+ # a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or
684
+ # SQL::AliasedExpression.
685
+ def alias_symbol(sym)
686
+ case sym
687
+ when Symbol
688
+ s, t, a = split_symbol(sym)
689
+ a || s ? (a || t).to_sym : sym
690
+ when String
691
+ sym.to_sym
692
+ when SQL::Identifier
693
+ sym.value.to_s.to_sym
694
+ when SQL::QualifiedIdentifier
695
+ alias_symbol(sym.column)
696
+ when SQL::AliasedExpression
697
+ alias_alias_symbol(sym.aliaz)
698
+ else
699
+ raise Error, "Invalid alias for alias_symbol: #{sym.inspect}"
700
+ end
701
+ end
702
+
633
703
  # Clone of this dataset usable in aggregate operations. Does
634
704
  # a from_self if dataset contains any parameters that would
635
705
  # affect normal aggregation, or just removes an existing
@@ -791,7 +861,7 @@ module Sequel
791
861
 
792
862
  # SQL fragment for Date, using the ISO8601 format.
793
863
  def literal_date(v)
794
- requires_sql_standard_datetimes? ? v.strftime("DATE '%Y-%m-%d'") : "'#{v}'"
864
+ v.strftime("#{'DATE ' if requires_sql_standard_datetimes?}'%Y-%m-%d'")
795
865
  end
796
866
 
797
867
  # SQL fragment for DateTime
@@ -823,6 +893,11 @@ module Sequel
823
893
  def literal_integer(v)
824
894
  v.to_s
825
895
  end
896
+
897
+ # SQL fragment for nil
898
+ def literal_nil
899
+ NULL
900
+ end
826
901
 
827
902
  # SQL fragment for a type of object not handled by Dataset#literal.
828
903
  # Calls sql_literal if object responds to it, otherwise raises an error.
@@ -996,8 +1071,8 @@ module Sequel
996
1071
 
997
1072
  # Modify the sql to limit the number of rows returned and offset
998
1073
  def select_limit_sql(sql)
999
- sql << " LIMIT #{@opts[:limit]}" if @opts[:limit]
1000
- sql << " OFFSET #{@opts[:offset]}" if @opts[:offset]
1074
+ sql << " LIMIT #{literal(@opts[:limit])}" if @opts[:limit]
1075
+ sql << " OFFSET #{literal(@opts[:offset])}" if @opts[:offset]
1001
1076
  end
1002
1077
 
1003
1078
  # Modify the sql to add the expressions to ORDER BY
@@ -1087,7 +1162,7 @@ module Sequel
1087
1162
  values = values.merge(opts[:overrides]) if opts[:overrides]
1088
1163
  # get values from hash
1089
1164
  values.map do |k, v|
1090
- "#{[String, Symbol].any?{|c| k.is_a?(c)} ? quote_identifier(k) : literal(k)} = #{literal(v)}"
1165
+ "#{k.is_a?(String) && !k.is_a?(LiteralString) ? quote_identifier(k) : literal(k)} = #{literal(v)}"
1091
1166
  end.join(COMMA_SEPARATOR)
1092
1167
  else
1093
1168
  # copy values verbatim
@@ -23,7 +23,14 @@ class String
23
23
  @plurals, @singulars, @uncountables = [], [], []
24
24
 
25
25
  class << self
26
- attr_reader :plurals, :singulars, :uncountables
26
+ # Array of 2 element arrays, first containing a regex, and the second containing a substitution pattern, used for plurization.
27
+ attr_reader :plurals
28
+
29
+ # Array of 2 element arrays, first containing a regex, and the second containing a substitution pattern, used for singularization.
30
+ attr_reader :singulars
31
+
32
+ # Array of strings for words were the singular form is the same as the plural form
33
+ attr_reader :uncountables
27
34
  end
28
35
 
29
36
  # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
@@ -11,7 +11,7 @@ module Sequel
11
11
  # * :same_db - Create a dump for the same database type, so
12
12
  # don't ignore errors if the index statements fail.
13
13
  def dump_indexes_migration(options={})
14
- ts = tables
14
+ ts = tables(options)
15
15
  <<END_MIG
16
16
  Class.new(Sequel::Migration) do
17
17
  def up
@@ -35,7 +35,7 @@ END_MIG
35
35
  # * :indexes - If set to false, don't dump indexes (they can be added
36
36
  # later via dump_index_migration).
37
37
  def dump_schema_migration(options={})
38
- ts = tables
38
+ ts = tables(options)
39
39
  <<END_MIG
40
40
  Class.new(Sequel::Migration) do
41
41
  def up
@@ -135,8 +135,7 @@ END_MIG
135
135
  when /\An?char(?:acter)?(?:\((\d+)\))?\z/o
136
136
  {:type=>String, :size=>($1.to_i if $1), :fixed=>true}
137
137
  when /\A(?:n?varchar|character varying|bpchar|string)(?:\((\d+)\))?\z/o
138
- s = ($1.to_i if $1)
139
- {:type=>String, :size=>(s == 255 ? nil : s)}
138
+ {:type=>String, :size=>($1.to_i if $1)}
140
139
  when /\A(?:small)?money\z/o
141
140
  {:type=>BigDecimal, :size=>[19,2]}
142
141
  when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?\z/o
@@ -763,7 +763,7 @@ module Sequel
763
763
  jt_join_type = opts[:graph_join_table_join_type]
764
764
  jt_graph_block = opts[:graph_join_table_block]
765
765
  opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
766
- ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.send(:eager_unique_table_alias, ds, join_table), :join_type=>jt_join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
766
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table), :join_type=>jt_join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
767
767
  ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
768
768
  end
769
769
 
@@ -1213,8 +1213,6 @@ module Sequel
1213
1213
  # Like eager, you need to call .all on the dataset for the eager loading to work. If you just
1214
1214
  # call each, you will get a normal graphed result back (a hash with model object values).
1215
1215
  def eager_graph(*associations)
1216
- table_name = model.table_name
1217
-
1218
1216
  ds = if @opts[:eager_graph]
1219
1217
  self
1220
1218
  else
@@ -1223,9 +1221,9 @@ module Sequel
1223
1221
  # :requirements - array of requirements for this association
1224
1222
  # :alias_association_type_map - the type of association for this association
1225
1223
  # :alias_association_name_map - the name of the association for this association
1226
- clone(:eager_graph=>{:requirements=>{}, :master=>table_name, :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
1224
+ clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
1227
1225
  end
1228
- ds.eager_graph_associations(ds, model, table_name, [], *associations)
1226
+ ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1229
1227
  end
1230
1228
 
1231
1229
  # Do not attempt to split the result set into associations,
@@ -1253,9 +1251,9 @@ module Sequel
1253
1251
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
1254
1252
  klass = r.associated_class
1255
1253
  assoc_name = r[:name]
1256
- assoc_table_alias = ds.eager_unique_table_alias(ds, assoc_name)
1254
+ assoc_table_alias = ds.unused_table_alias(assoc_name)
1257
1255
  ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
1258
- ds = ds.order_more(*Array(r[:order]).map{|c| eager_graph_qualify_order(assoc_table_alias, c)}) if r[:order] and r[:order_eager_graph]
1256
+ ds = ds.order_more(*qualified_expression(r[:order], assoc_table_alias)) if r[:order] and r[:order_eager_graph]
1259
1257
  eager_graph = ds.opts[:eager_graph]
1260
1258
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
1261
1259
  eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
@@ -1354,25 +1352,6 @@ module Sequel
1354
1352
  record_graphs.replace(records)
1355
1353
  end
1356
1354
 
1357
- # Creates a unique table alias that hasn't already been used in the query.
1358
- # Will either be the table_alias itself or table_alias_N for some integer
1359
- # N (starting at 0 and increasing until an unused one is found).
1360
- def eager_unique_table_alias(ds, table_alias)
1361
- used_aliases = ds.opts[:from]
1362
- used_aliases += ds.opts[:join].map{|j| j.table_alias || j.table} if ds.opts[:join]
1363
- graph = ds.opts[:graph]
1364
- used_aliases += graph[:table_aliases].keys if graph
1365
- if used_aliases.include?(table_alias)
1366
- i = 0
1367
- loop do
1368
- ta = :"#{table_alias}_#{i}"
1369
- return ta unless used_aliases.include?(ta)
1370
- i += 1
1371
- end
1372
- end
1373
- table_alias
1374
- end
1375
-
1376
1355
  private
1377
1356
 
1378
1357
  # Make sure the association is valid for this model, and return the related AssociationReflection.
@@ -1433,23 +1412,6 @@ module Sequel
1433
1412
  end
1434
1413
  end
1435
1414
 
1436
- # Qualify the given expression if necessary. The only expressions which are qualified are
1437
- # unqualified symbols and identifiers, either of which may by sorted.
1438
- def eager_graph_qualify_order(table_alias, expression)
1439
- case expression
1440
- when Symbol
1441
- table, column, aliaz = split_symbol(expression)
1442
- raise(Sequel::Error, "Can't use an aliased expression in the :order option") if aliaz
1443
- table ? expression : Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
1444
- when Sequel::SQL::Identifier
1445
- Sequel::SQL::QualifiedIdentifier.new(table_alias, expression)
1446
- when Sequel::SQL::OrderedExpression
1447
- Sequel::SQL::OrderedExpression.new(eager_graph_qualify_order(table_alias, expression.expression), expression.descending)
1448
- else
1449
- expression
1450
- end
1451
- end
1452
-
1453
1415
  # Eagerly load all specified associations
1454
1416
  def eager_load(a, eager_assoc=@opts[:eager])
1455
1417
  return if a.empty?