sequel 3.34.1 → 3.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/CHANGELOG +52 -0
  2. data/README.rdoc +3 -1
  3. data/Rakefile +2 -10
  4. data/doc/active_record.rdoc +1 -0
  5. data/doc/migration.rdoc +18 -7
  6. data/doc/model_hooks.rdoc +6 -0
  7. data/doc/opening_databases.rdoc +3 -0
  8. data/doc/prepared_statements.rdoc +0 -1
  9. data/doc/release_notes/3.35.0.txt +144 -0
  10. data/doc/schema_modification.rdoc +16 -1
  11. data/doc/thread_safety.rdoc +17 -0
  12. data/lib/sequel/adapters/do.rb +2 -2
  13. data/lib/sequel/adapters/do/postgres.rb +1 -52
  14. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  15. data/lib/sequel/adapters/firebird.rb +1 -1
  16. data/lib/sequel/adapters/ibmdb.rb +2 -2
  17. data/lib/sequel/adapters/jdbc.rb +23 -19
  18. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  19. data/lib/sequel/adapters/jdbc/derby.rb +29 -2
  20. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  21. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  22. data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
  23. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  24. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  25. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  26. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -35
  27. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  28. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/transactions.rb +4 -4
  30. data/lib/sequel/adapters/mysql2.rb +1 -1
  31. data/lib/sequel/adapters/odbc.rb +3 -3
  32. data/lib/sequel/adapters/odbc/mssql.rb +14 -1
  33. data/lib/sequel/adapters/oracle.rb +6 -18
  34. data/lib/sequel/adapters/postgres.rb +36 -53
  35. data/lib/sequel/adapters/shared/db2.rb +16 -2
  36. data/lib/sequel/adapters/shared/mssql.rb +40 -9
  37. data/lib/sequel/adapters/shared/mysql.rb +16 -4
  38. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
  39. data/lib/sequel/adapters/shared/oracle.rb +2 -0
  40. data/lib/sequel/adapters/shared/postgres.rb +135 -211
  41. data/lib/sequel/adapters/sqlite.rb +2 -2
  42. data/lib/sequel/adapters/swift.rb +1 -1
  43. data/lib/sequel/adapters/swift/postgres.rb +1 -71
  44. data/lib/sequel/adapters/tinytds.rb +3 -3
  45. data/lib/sequel/core.rb +27 -4
  46. data/lib/sequel/database/connecting.rb +7 -8
  47. data/lib/sequel/database/logging.rb +6 -1
  48. data/lib/sequel/database/misc.rb +20 -4
  49. data/lib/sequel/database/query.rb +38 -18
  50. data/lib/sequel/database/schema_generator.rb +5 -2
  51. data/lib/sequel/database/schema_methods.rb +34 -8
  52. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  53. data/lib/sequel/dataset/sql.rb +18 -24
  54. data/lib/sequel/extensions/core_extensions.rb +0 -23
  55. data/lib/sequel/extensions/migration.rb +22 -8
  56. data/lib/sequel/extensions/pg_auto_parameterize.rb +4 -0
  57. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  58. data/lib/sequel/model.rb +2 -2
  59. data/lib/sequel/model/associations.rb +95 -70
  60. data/lib/sequel/model/base.rb +16 -18
  61. data/lib/sequel/plugins/dirty.rb +214 -0
  62. data/lib/sequel/plugins/identity_map.rb +1 -1
  63. data/lib/sequel/plugins/json_serializer.rb +16 -1
  64. data/lib/sequel/plugins/many_through_many.rb +22 -32
  65. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +2 -2
  66. data/lib/sequel/plugins/prepared_statements.rb +22 -8
  67. data/lib/sequel/plugins/prepared_statements_associations.rb +2 -3
  68. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  69. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  70. data/lib/sequel/plugins/subclasses.rb +10 -2
  71. data/lib/sequel/plugins/timestamps.rb +1 -1
  72. data/lib/sequel/plugins/xml_serializer.rb +12 -1
  73. data/lib/sequel/sql.rb +1 -1
  74. data/lib/sequel/version.rb +2 -2
  75. data/spec/adapters/postgres_spec.rb +30 -79
  76. data/spec/core/database_spec.rb +46 -2
  77. data/spec/core/dataset_spec.rb +28 -22
  78. data/spec/core/schema_generator_spec.rb +1 -1
  79. data/spec/core/schema_spec.rb +51 -0
  80. data/spec/extensions/arbitrary_servers_spec.rb +0 -4
  81. data/spec/extensions/association_autoreloading_spec.rb +17 -0
  82. data/spec/extensions/association_proxies_spec.rb +4 -4
  83. data/spec/extensions/core_extensions_spec.rb +1 -24
  84. data/spec/extensions/dirty_spec.rb +155 -0
  85. data/spec/extensions/json_serializer_spec.rb +13 -0
  86. data/spec/extensions/migration_spec.rb +28 -15
  87. data/spec/extensions/named_timezones_spec.rb +6 -8
  88. data/spec/extensions/pg_auto_parameterize_spec.rb +6 -5
  89. data/spec/extensions/schema_dumper_spec.rb +3 -1
  90. data/spec/extensions/xml_serializer_spec.rb +13 -0
  91. data/spec/files/{transactionless_migrations → transaction_specified_migrations}/001_create_alt_basic.rb +1 -1
  92. data/spec/files/{transactionless_migrations → transaction_specified_migrations}/002_create_basic.rb +0 -0
  93. data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/001_create_alt_basic.rb +0 -0
  94. data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/002_create_basic.rb +0 -0
  95. data/spec/integration/associations_test.rb +5 -7
  96. data/spec/integration/dataset_test.rb +25 -7
  97. data/spec/integration/plugin_test.rb +1 -1
  98. data/spec/integration/schema_test.rb +16 -1
  99. data/spec/model/associations_spec.rb +2 -2
  100. metadata +14 -9
  101. data/lib/sequel/adapters/odbc/db2.rb +0 -17
@@ -83,7 +83,8 @@ module Sequel
83
83
  # reference table will use for its foreign key a value that does not
84
84
  # exists(yet) on referenced table. Basically it adds
85
85
  # DEFERRABLE INITIALLY DEFERRED on key creation.
86
- # :index :: Create an index on this column.
86
+ # :index :: Create an index on this column. If given a hash, use the hash as the
87
+ # options for the index.
87
88
  # :key :: For foreign key columns, the column in the associated table
88
89
  # that this column references. Unnecessary if this column
89
90
  # references the primary key of the associated table, except if you are
@@ -107,7 +108,9 @@ module Sequel
107
108
  # columns.
108
109
  def column(name, type, opts = {})
109
110
  columns << {:name => name, :type => type}.merge(opts)
110
- index(name) if opts[:index]
111
+ if index_opts = opts[:index]
112
+ index(name, index_opts.is_a?(Hash) ? index_opts : {})
113
+ end
111
114
  end
112
115
 
113
116
  # Adds a named constraint (or unnamed if name is nil) to the DDL,
@@ -132,19 +132,27 @@ module Sequel
132
132
  # end
133
133
  #
134
134
  # Options:
135
- # :temp :: Create the table as a temporary table.
135
+ # :as :: Create the table using the value, which should be either a
136
+ # dataset or a literal SQL string. If this option is used,
137
+ # a block should not be given to the method.
136
138
  # :ignore_index_errors :: Ignore any errors when creating indexes.
139
+ # :temp :: Create the table as a temporary table.
137
140
  #
138
- # See <tt>Schema::Generator</tt> and the {"Migrations and Schema Modification" guide}[link:files/doc/migration_rdoc.html].
141
+ # See <tt>Schema::Generator</tt> and the {"Schema Modification" guide}[link:files/doc/schema_modification_rdoc.html].
139
142
  def create_table(name, options={}, &block)
140
143
  remove_cached_schema(name)
141
144
  options = {:generator=>options} if options.is_a?(Schema::Generator)
142
- generator = options[:generator] || Schema::Generator.new(self, &block)
143
- create_table_from_generator(name, generator, options)
144
- create_table_indexes_from_generator(name, generator, options)
145
- nil
145
+ if sql = options[:as]
146
+ raise(Error, "can't provide both :as option and block to create_table") if block
147
+ create_table_as(name, sql, options)
148
+ else
149
+ generator = options[:generator] || Schema::Generator.new(self, &block)
150
+ create_table_from_generator(name, generator, options)
151
+ create_table_indexes_from_generator(name, generator, options)
152
+ nil
153
+ end
146
154
  end
147
-
155
+
148
156
  # Forcibly create a table, attempting to drop it if it already exists, then creating it.
149
157
  #
150
158
  # DB.create_table!(:a){Integer :a}
@@ -466,7 +474,25 @@ module Sequel
466
474
 
467
475
  # DDL statement for creating a table with the given name, columns, and options
468
476
  def create_table_sql(name, generator, options)
469
- "CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)} (#{column_list_sql(generator)})"
477
+ "#{create_table_prefix_sql(name, options)} (#{column_list_sql(generator)})"
478
+ end
479
+
480
+ # Run a command to create the table with the given name from the given
481
+ # SELECT sql statement.
482
+ def create_table_as(name, sql, options)
483
+ sql = sql.sql if sql.is_a?(Sequel::Dataset)
484
+ run(create_table_as_sql(name, sql, options))
485
+ end
486
+
487
+ # DDL statement for creating a table from the result of a SELECT statement.
488
+ # +sql+ should be a string representing a SELECT query.
489
+ def create_table_as_sql(name, sql, options)
490
+ "#{create_table_prefix_sql(name, options)} AS #{sql}"
491
+ end
492
+
493
+ # DDL statement for creating a table with the given name, columns, and options
494
+ def create_table_prefix_sql(name, options)
495
+ "CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
470
496
  end
471
497
 
472
498
  # Default index name for the table and columns, may be too long
@@ -247,7 +247,7 @@ module Sequel
247
247
  # DB.call(:select_by_name, :name=>'Blah') # Same thing
248
248
  def prepare(type, name=nil, *values)
249
249
  ps = to_prepared_statement(type, values)
250
- db.prepared_statements[name] = ps if name
250
+ db.set_prepared_statement(name, ps) if name
251
251
  ps
252
252
  end
253
253
 
@@ -152,8 +152,8 @@ module Sequel
152
152
  if opts[:sql]
153
153
  static_sql(opts[:sql])
154
154
  else
155
- check_modification_allowed!
156
- raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where]
155
+ check_truncation_allowed!
156
+ raise(InvalidOperation, "Can't truncate filtered datasets") if opts[:where] || opts[:having]
157
157
  _truncate_sql(source_list(opts[:from]))
158
158
  end
159
159
  end
@@ -190,7 +190,6 @@ module Sequel
190
190
  ARRAY_EMPTY = '(NULL)'.freeze
191
191
  AS = ' AS '.freeze
192
192
  ASC = ' ASC'.freeze
193
- BACKSLASH_RE = /\\/.freeze
194
193
  BOOL_FALSE = "'f'".freeze
195
194
  BOOL_TRUE = "'t'".freeze
196
195
  BRACKET_CLOSE = ']'.freeze
@@ -257,7 +256,6 @@ module Sequel
257
256
  PAREN_OPEN = '('.freeze
258
257
  PAREN_SPACE_OPEN = ' ('.freeze
259
258
  PARTITION_BY = "PARTITION BY ".freeze
260
- QUAD_BACKSLASH = "\\\\\\\\".freeze
261
259
  QUALIFY_KEYS = [:select, :where, :having, :order, :group]
262
260
  QUESTION_MARK = '?'.freeze
263
261
  QUESTION_MARK_RE = /\?/.freeze
@@ -279,7 +277,6 @@ module Sequel
279
277
  UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'update table set where')
280
278
  USING = ' USING ('.freeze
281
279
  VALUES = " VALUES ".freeze
282
- V187 = '1.8.7'.freeze
283
280
  V190 = '1.9.0'.freeze
284
281
  WHERE = " WHERE ".freeze
285
282
 
@@ -558,15 +555,11 @@ module Sequel
558
555
  sql << PAREN_OPEN if pls.parens
559
556
  if args.is_a?(Hash)
560
557
  re = /:(#{args.keys.map{|k| Regexp.escape(k.to_s)}.join('|')})\b/
561
- if RUBY_VERSION >= V187
562
- loop do
563
- previous, q, str = str.partition(re)
564
- sql << previous
565
- literal_append(sql, args[($1||q[1..-1].to_s).to_sym]) unless q.empty?
566
- break if str.empty?
567
- end
568
- else
569
- sql << str.gsub(re){literal(args[$1.to_sym])}
558
+ loop do
559
+ previous, q, str = str.partition(re)
560
+ sql << previous
561
+ literal_append(sql, args[($1||q[1..-1].to_s).to_sym]) unless q.empty?
562
+ break if str.empty?
570
563
  end
571
564
  elsif str.is_a?(Array)
572
565
  len = args.length
@@ -576,15 +569,11 @@ module Sequel
576
569
  end
577
570
  else
578
571
  i = -1
579
- if RUBY_VERSION >= V187
580
- loop do
581
- previous, q, str = str.partition(QUESTION_MARK)
582
- sql << previous
583
- literal_append(sql, args.at(i+=1)) unless q.empty?
584
- break if str.empty?
585
- end
586
- else
587
- sql << str.gsub(QUESTION_MARK_RE){literal(args.at(i+=1))}
572
+ loop do
573
+ previous, q, str = str.partition(QUESTION_MARK)
574
+ sql << previous
575
+ literal_append(sql, args.at(i+=1)) unless q.empty?
576
+ break if str.empty?
588
577
  end
589
578
  end
590
579
  sql << PAREN_CLOSE if pls.parens
@@ -801,6 +790,11 @@ module Sequel
801
790
  raise(InvalidOperation, "Joined datasets cannot be modified") if !supports_modifying_joins? && joined_dataset?
802
791
  end
803
792
 
793
+ # Alias of check_modification_allowed!
794
+ def check_truncation_allowed!
795
+ check_modification_allowed!
796
+ end
797
+
804
798
  # Prepare an SQL statement by calling all clause methods for the given statement type.
805
799
  def clause_sql(type)
806
800
  sql = @opts[:append_sql] || sql_string_origin
@@ -1102,7 +1096,7 @@ module Sequel
1102
1096
 
1103
1097
  # SQL fragment for String. Doubles \ and ' by default.
1104
1098
  def literal_string_append(sql, v)
1105
- sql << APOS << v.gsub(BACKSLASH_RE, QUAD_BACKSLASH).gsub(APOS_RE, DOUBLE_APOS) << APOS
1099
+ sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
1106
1100
  end
1107
1101
 
1108
1102
  # Converts a symbol into a column name. This method supports underscore
@@ -21,20 +21,6 @@ class Array
21
21
  Sequel.~(self)
22
22
  end
23
23
 
24
- # +true+ if the array is not empty and all of its elements are
25
- # arrays of size 2, +false+ otherwise. This is used to determine if the array
26
- # could be a specifier of conditions, used similarly to a hash
27
- # but allowing for duplicate keys and a specific order.
28
- #
29
- # [].to_a.all_two_pairs? # => false
30
- # [:a].to_a.all_two_pairs? # => false
31
- # [[:b]].to_a.all_two_pairs? # => false
32
- # [[:a, 1]].to_a.all_two_pairs? # => true
33
- def all_two_pairs?
34
- warn('Array#all_two_pairs? is deprecated and will be removed in Sequel 3.35.0')
35
- !empty? && all?{|i| (Array === i) && (i.length == 2)}
36
- end
37
-
38
24
  # Return a <tt>Sequel::SQL::CaseExpression</tt> with this array as the conditions and the given
39
25
  # default value and expression.
40
26
  #
@@ -103,15 +89,6 @@ class Array
103
89
  def sql_string_join(joiner=nil)
104
90
  Sequel.join(self, joiner)
105
91
  end
106
-
107
- private
108
-
109
- # Raise an error if this array is not made up all two element arrays, otherwise create a <tt>Sequel::SQL::BooleanExpression</tt> from this array.
110
- def sql_expr_if_all_two_pairs(*args)
111
- warn('Array#sql_expr_if_all_two_pairs? is deprecated and will be removed in Sequel 3.35.0')
112
- raise(Sequel::Error, 'Not all elements of the array are arrays of size 2, so it cannot be converted to an SQL expression') unless all_two_pairs?
113
- ::Sequel::SQL::BooleanExpression.from_value_pairs(self, *args)
114
- end
115
92
  end
116
93
 
117
94
  # Sequel extends +Hash+ to add methods to implement the SQL DSL.
@@ -44,9 +44,9 @@ module Sequel
44
44
  descendants << base
45
45
  end
46
46
 
47
- # Always use transactions for old-style migrations.
47
+ # Don't allow transaction overriding in old migrations.
48
48
  def self.use_transactions
49
- true
49
+ nil
50
50
  end
51
51
 
52
52
  # The default down action does nothing
@@ -74,12 +74,13 @@ module Sequel
74
74
  # Proc used for the up action
75
75
  attr_accessor :up
76
76
 
77
- # Whether to use transactions for this migration, true by default.
77
+ # Whether to use transactions for this migration, default depends on the
78
+ # database.
78
79
  attr_accessor :use_transactions
79
80
 
80
- # Use transactions by default
81
+ # Don't set transaction use by default.
81
82
  def initialize
82
- @use_transactions = true
83
+ @use_transactions = nil
83
84
  end
84
85
 
85
86
  # Apply the appropriate block on the +Database+
@@ -118,6 +119,11 @@ module Sequel
118
119
  migration.use_transactions = false
119
120
  end
120
121
 
122
+ # Enable the use of transactions for the related migration
123
+ def transaction
124
+ migration.use_transactions = true
125
+ end
126
+
121
127
  # Defines the migration's up action.
122
128
  def up(&block)
123
129
  migration.up = block
@@ -414,9 +420,17 @@ module Sequel
414
420
  # If transactions should be used for the migration, yield to the block
415
421
  # inside a transaction. Otherwise, just yield to the block.
416
422
  def checked_transaction(migration, &block)
417
- if @use_transactions == false
418
- yield
419
- elsif migration.use_transactions || @use_transactions
423
+ use_trans = if @use_transactions.nil?
424
+ if migration.use_transactions.nil?
425
+ @db.supports_transactional_ddl?
426
+ else
427
+ migration.use_transactions
428
+ end
429
+ else
430
+ @use_transactions
431
+ end
432
+
433
+ if use_trans
420
434
  db.transaction(&block)
421
435
  else
422
436
  yield
@@ -153,6 +153,10 @@ module Sequel
153
153
  end
154
154
  end
155
155
 
156
+ def use_cursor(*)
157
+ super.no_auto_parameterize
158
+ end
159
+
156
160
  protected
157
161
 
158
162
  # Disable automatic parameterization for prepared statements,
@@ -175,7 +175,7 @@ END_MIG
175
175
  {:type=>BigDecimal, :size=>(s.empty? ? nil : s)}
176
176
  when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/o
177
177
  {:type=>File, :size=>($1.to_i if $1)}
178
- when 'year'
178
+ when /\A(?:year|(?:int )?identity)\z/o
179
179
  {:type=>Integer}
180
180
  else
181
181
  {:type=>String}
data/lib/sequel/model.rb CHANGED
@@ -35,7 +35,7 @@ module Sequel
35
35
  # dataset # => DB1[:comments]
36
36
  # end
37
37
  def self.Model(source)
38
- if Sequel::Model.cache_anonymous_models && (klass = Model::ANONYMOUS_MODEL_CLASSES[source])
38
+ if Sequel::Model.cache_anonymous_models && (klass = Sequel.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source]})
39
39
  return klass
40
40
  end
41
41
  klass = if source.is_a?(Database)
@@ -45,7 +45,7 @@ module Sequel
45
45
  else
46
46
  Class.new(Model).set_dataset(source)
47
47
  end
48
- Model::ANONYMOUS_MODEL_CLASSES[source] = klass if Sequel::Model.cache_anonymous_models
48
+ Sequel.synchronize{Model::ANONYMOUS_MODEL_CLASSES[source] = klass} if Sequel::Model.cache_anonymous_models
49
49
  klass
50
50
  end
51
51
 
@@ -54,7 +54,7 @@ module Sequel
54
54
 
55
55
  # The class associated to the current model class via this association
56
56
  def associated_class
57
- self[:class] ||= constantize(self[:class_name])
57
+ cached_fetch(:class){constantize(self[:class_name])}
58
58
  end
59
59
 
60
60
  # Whether this association can have associated objects, given the current
@@ -81,9 +81,9 @@ module Sequel
81
81
 
82
82
  # The eager limit strategy to use for this dataset.
83
83
  def eager_limit_strategy
84
- fetch(:_eager_limit_strategy) do
85
- self[:_eager_limit_strategy] = if self[:limit]
86
- case s = self.fetch(:eager_limit_strategy){self[:model].default_eager_limit_strategy || :ruby}
84
+ cached_fetch(:_eager_limit_strategy) do
85
+ if self[:limit]
86
+ case s = cached_fetch(:eager_limit_strategy){self[:model].default_eager_limit_strategy || :ruby}
87
87
  when true
88
88
  ds = associated_class.dataset
89
89
  if ds.supports_window_functions?
@@ -151,16 +151,19 @@ module Sequel
151
151
  # to populate reciprocal associations. For example, when you do this_artist.add_album(album)
152
152
  # it sets album.artist to this_artist.
153
153
  def reciprocal
154
- return self[:reciprocal] if include?(:reciprocal)
155
- r_types = Array(reciprocal_type)
156
- keys = self[:keys]
157
- associated_class.all_association_reflections.each do |assoc_reflect|
158
- if r_types.include?(assoc_reflect[:type]) && assoc_reflect[:keys] == keys && assoc_reflect.associated_class == self[:model]
159
- self[:reciprocal_type] = assoc_reflect[:type]
160
- return self[:reciprocal] = assoc_reflect[:name]
154
+ cached_fetch(:reciprocal) do
155
+ r_types = Array(reciprocal_type)
156
+ keys = self[:keys]
157
+ recip = nil
158
+ associated_class.all_association_reflections.each do |assoc_reflect|
159
+ if r_types.include?(assoc_reflect[:type]) && assoc_reflect[:keys] == keys && assoc_reflect.associated_class == self[:model]
160
+ cached_set(:reciprocal_type, assoc_reflect[:type])
161
+ recip = assoc_reflect[:name]
162
+ break
163
+ end
161
164
  end
165
+ recip
162
166
  end
163
- self[:reciprocal] = nil
164
167
  end
165
168
 
166
169
  # Whether the reciprocal of this association returns an array of objects instead of a single object,
@@ -214,6 +217,39 @@ module Sequel
214
217
 
215
218
  private
216
219
 
220
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
221
+ # On non-GVL rubies, assume the need to synchronize access. Store the key
222
+ # in a special sub-hash that always uses this method to synchronize access.
223
+ def cached_fetch(key)
224
+ fetch(key) do
225
+ h = self[:cache]
226
+ Sequel.synchronize{return h[key] if h.has_key?(key)}
227
+ value = yield
228
+ Sequel.synchronize{h[key] = value}
229
+ end
230
+ end
231
+
232
+ # Cache the value at the given key, synchronizing access.
233
+ def cached_set(key, value)
234
+ h = self[:cache]
235
+ Sequel.synchronize{h[key] = value}
236
+ end
237
+ else
238
+ # On MRI, use a plain fetch, since the GVL will synchronize access.
239
+ def cached_fetch(key)
240
+ fetch(key) do
241
+ h = self[:cache]
242
+ h.fetch(key){h[key] = yield}
243
+ end
244
+ end
245
+
246
+ # On MRI, just set the value at the key in the cache, since the GVL
247
+ # will synchronize access.
248
+ def cached_set(key, value)
249
+ self[:cache][key] = value
250
+ end
251
+ end
252
+
217
253
  # If +s+ is an array, map +s+ over the block. Otherwise, just call the
218
254
  # block with +s+.
219
255
  def transform(s)
@@ -254,35 +290,35 @@ module Sequel
254
290
 
255
291
  # The key to use for the key hash when eager loading
256
292
  def eager_loader_key
257
- self[:eager_loader_key] ||= self[:key]
293
+ cached_fetch(:eager_loader_key){self[:key]}
258
294
  end
259
295
 
260
296
  # The column(s) in the associated table that the key in the current table references (either a symbol or an array).
261
297
  def primary_key
262
- self[:primary_key] ||= associated_class.primary_key
298
+ cached_fetch(:primary_key){associated_class.primary_key}
263
299
  end
264
300
 
265
301
  # The columns in the associated table that the key in the current table references (always an array).
266
302
  def primary_keys
267
- self[:primary_keys] ||= Array(primary_key)
303
+ cached_fetch(:primary_keys){Array(primary_key)}
268
304
  end
269
305
  alias associated_object_keys primary_keys
270
306
 
271
307
  # The method symbol or array of method symbols to call on the associated object
272
308
  # to get the value to use for the foreign keys.
273
309
  def primary_key_method
274
- self[:primary_key_method] ||= primary_key
310
+ cached_fetch(:primary_key_method){primary_key}
275
311
  end
276
312
 
277
313
  # The array of method symbols to call on the associated object
278
314
  # to get the value to use for the foreign keys.
279
315
  def primary_key_methods
280
- self[:primary_key_methods] ||= Array(primary_key_method)
316
+ cached_fetch(:primary_key_methods){Array(primary_key_method)}
281
317
  end
282
318
 
283
319
  # #primary_key qualified by the associated table
284
320
  def qualified_primary_key
285
- self[:qualified_primary_key] ||= self[:qualify] == false ? primary_key : qualify_assoc(primary_key)
321
+ cached_fetch(:qualified_primary_key){self[:qualify] == false ? primary_key : qualify_assoc(primary_key)}
286
322
  end
287
323
 
288
324
  # True only if the reciprocal is a one_to_many association.
@@ -299,7 +335,7 @@ module Sequel
299
335
  # True only if the reciprocal is a one_to_one association.
300
336
  def set_reciprocal_to_self?
301
337
  reciprocal
302
- self[:reciprocal_type] == :one_to_one
338
+ reciprocal_type == :one_to_one
303
339
  end
304
340
 
305
341
  private
@@ -307,7 +343,7 @@ module Sequel
307
343
  # The reciprocal type of a many_to_one association is either
308
344
  # a one_to_many or a one_to_one association.
309
345
  def reciprocal_type
310
- self[:reciprocal_type] ||= [:one_to_many, :one_to_one]
346
+ cached_fetch(:reciprocal_type){[:one_to_many, :one_to_one]}
311
347
  end
312
348
  end
313
349
 
@@ -333,12 +369,12 @@ module Sequel
333
369
 
334
370
  # The key to use for the key hash when eager loading
335
371
  def eager_loader_key
336
- self[:eager_loader_key] ||= primary_key
372
+ cached_fetch(:eager_loader_key){primary_key}
337
373
  end
338
374
 
339
375
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
340
376
  def eager_loading_predicate_key
341
- self[:eager_loading_predicate_key] ||= qualify_assoc(self[:key])
377
+ cached_fetch(:eager_loading_predicate_key){qualify_assoc(self[:key])}
342
378
  end
343
379
  alias qualified_key eager_loading_predicate_key
344
380
 
@@ -349,7 +385,7 @@ module Sequel
349
385
 
350
386
  # #primary_key qualified by the current table
351
387
  def qualified_primary_key
352
- self[:qualified_primary_key] ||= qualify_cur(primary_key)
388
+ cached_fetch(:qualified_primary_key){qualify_cur(primary_key)}
353
389
  end
354
390
 
355
391
  # Whether the reciprocal of this association returns an array of objects instead of a single object,
@@ -387,8 +423,8 @@ module Sequel
387
423
  # one_to_one associations don't use an eager limit strategy by default, but
388
424
  # support both DISTINCT ON and window functions as strategies.
389
425
  def eager_limit_strategy
390
- fetch(:_eager_limit_strategy) do
391
- self[:_eager_limit_strategy] = case s = self[:eager_limit_strategy]
426
+ cached_fetch(:_eager_limit_strategy) do
427
+ case s = self[:eager_limit_strategy]
392
428
  when Symbol
393
429
  s
394
430
  when true
@@ -464,19 +500,19 @@ module Sequel
464
500
 
465
501
  # The key to use for the key hash when eager loading
466
502
  def eager_loader_key
467
- self[:eager_loader_key] ||= self[:left_primary_key]
503
+ cached_fetch(:eager_loader_key){self[:left_primary_key]}
468
504
  end
469
505
 
470
506
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
471
507
  # The left key qualified by the join table.
472
508
  def eager_loading_predicate_key
473
- self[:eager_loading_predicate_key] ||= qualify(join_table_alias, self[:left_key])
509
+ cached_fetch(:eager_loading_predicate_key){qualify(join_table_alias, self[:left_key])}
474
510
  end
475
511
  alias qualified_left_key eager_loading_predicate_key
476
512
 
477
513
  # The right key qualified by the join table.
478
514
  def qualified_right_key
479
- self[:qualified_right_key] ||= qualify(join_table_alias, self[:right_key])
515
+ cached_fetch(:qualified_right_key){qualify(join_table_alias, self[:right_key])}
480
516
  end
481
517
 
482
518
  # many_to_many associations need to select a key in an associated table to eagerly load
@@ -487,18 +523,15 @@ module Sequel
487
523
  # The source of the join table. This is the join table itself, unless it
488
524
  # is aliased, in which case it is the unaliased part.
489
525
  def join_table_source
490
- fetch(:join_table_source) do
491
- split_join_table_alias
492
- self[:join_table_source]
493
- end
526
+ cached_fetch(:join_table_source){split_join_table_alias[0]}
494
527
  end
495
528
 
496
529
  # The join table itself, unless it is aliased, in which case this
497
530
  # is the alias.
498
531
  def join_table_alias
499
- fetch(:join_table_alias) do
500
- split_join_table_alias
501
- self[:join_table_alias]
532
+ cached_fetch(:join_table_alias) do
533
+ s, a = split_join_table_alias
534
+ a || s
502
535
  end
503
536
  end
504
537
  alias associated_key_table join_table_alias
@@ -511,60 +544,60 @@ module Sequel
511
544
 
512
545
  # Returns the reciprocal association symbol, if one exists.
513
546
  def reciprocal
514
- return self[:reciprocal] if include?(:reciprocal)
515
- left_keys = self[:left_keys]
516
- right_keys = self[:right_keys]
517
- join_table = self[:join_table]
518
- associated_class.all_association_reflections.each do |assoc_reflect|
519
- if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_keys] == right_keys &&
520
- assoc_reflect[:right_keys] == left_keys && assoc_reflect[:join_table] == join_table &&
521
- assoc_reflect.associated_class == self[:model]
522
- return self[:reciprocal] = assoc_reflect[:name]
547
+ cached_fetch(:reciprocal) do
548
+ left_keys = self[:left_keys]
549
+ right_keys = self[:right_keys]
550
+ join_table = self[:join_table]
551
+ recip = nil
552
+ associated_class.all_association_reflections.each do |assoc_reflect|
553
+ if assoc_reflect[:type] == :many_to_many && assoc_reflect[:left_keys] == right_keys &&
554
+ assoc_reflect[:right_keys] == left_keys && assoc_reflect[:join_table] == join_table &&
555
+ assoc_reflect.associated_class == self[:model]
556
+ recip = assoc_reflect[:name]
557
+ break
558
+ end
523
559
  end
560
+ recip
524
561
  end
525
- self[:reciprocal] = nil
526
562
  end
527
563
 
528
564
  # #right_primary_key qualified by the associated table
529
565
  def qualified_right_primary_key
530
- self[:qualified_right_primary_key] ||= qualify_assoc(right_primary_key)
566
+ cached_fetch(:qualified_right_primary_key){qualify_assoc(right_primary_key)}
531
567
  end
532
568
 
533
569
  # The primary key column(s) to use in the associated table (can be symbol or array).
534
570
  def right_primary_key
535
- self[:right_primary_key] ||= associated_class.primary_key
571
+ cached_fetch(:right_primary_key){associated_class.primary_key}
536
572
  end
537
573
 
538
574
  # The primary key columns to use in the associated table (always array).
539
575
  def right_primary_keys
540
- self[:right_primary_keys] ||= Array(right_primary_key)
576
+ cached_fetch(:right_primary_keys){Array(right_primary_key)}
541
577
  end
542
578
 
543
579
  # The method symbol or array of method symbols to call on the associated objects
544
580
  # to get the foreign key values for the join table.
545
581
  def right_primary_key_method
546
- self[:right_primary_key_method] ||= right_primary_key
582
+ cached_fetch(:right_primary_key_method){right_primary_key}
547
583
  end
548
584
 
549
585
  # The array of method symbols to call on the associated objects
550
586
  # to get the foreign key values for the join table.
551
587
  def right_primary_key_methods
552
- self[:right_primary_key_methods] ||= Array(right_primary_key_method)
588
+ cached_fetch(:right_primary_key_methods){Array(right_primary_key_method)}
553
589
  end
554
590
 
555
591
  # The columns to select when loading the association, associated_class.table_name.* by default.
556
592
  def select
557
- return self[:select] if include?(:select)
558
- self[:select] ||= Sequel::SQL::ColumnAll.new(associated_class.table_name)
593
+ cached_fetch(:select){Sequel::SQL::ColumnAll.new(associated_class.table_name)}
559
594
  end
560
595
 
561
596
  private
562
597
 
563
598
  # Split the join table into source and alias parts.
564
599
  def split_join_table_alias
565
- s, a = associated_class.dataset.split_alias(self[:join_table])
566
- self[:join_table_source] = s
567
- self[:join_table_alias] = a || s
600
+ associated_class.dataset.split_alias(self[:join_table])
568
601
  end
569
602
  end
570
603
 
@@ -862,7 +895,7 @@ module Sequel
862
895
  # dup early so we don't modify opts
863
896
  orig_opts = opts.dup
864
897
  orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
865
- opts = orig_opts.merge(:type => type, :name => name, :cache => true, :model => self)
898
+ opts = orig_opts.merge(:type => type, :name => name, :cache=>{}, :model => self)
866
899
  opts[:block] = block if block
867
900
  opts = assoc_class.new.merge!(opts)
868
901
  opts[:eager_block] = block unless opts.include?(:eager_block)
@@ -878,6 +911,9 @@ module Sequel
878
911
  end
879
912
  late_binding_class_option(opts, opts.returns_array? ? singularize(name) : name)
880
913
 
914
+ # Remove :class entry if it exists and is nil, to work with cached_fetch
915
+ opts.delete(:class) unless opts[:class]
916
+
881
917
  send(:"def_#{type}", opts)
882
918
 
883
919
  orig_opts.delete(:clone)
@@ -1047,20 +1083,9 @@ module Sequel
1047
1083
  def_association_method(opts)
1048
1084
  end
1049
1085
 
1050
- # Adds the association method to the association methods module. Be backwards
1051
- # compatible with ruby 1.8.6, which doesn't support blocks taking block arguments.
1052
- if RUBY_VERSION >= '1.8.7'
1053
- class_eval <<-END, __FILE__, __LINE__+1
1054
- def def_association_method(opts)
1055
- association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
1056
- end
1057
- END
1058
- else
1059
- class_eval <<-END, __FILE__, __LINE__+1
1060
- def def_association_method(opts)
1061
- association_module_def(opts.association_method, opts){|*dynamic_opts| load_associated_objects(opts, dynamic_opts[0])}
1062
- end
1063
- END
1086
+ # Adds the association method to the association methods module.
1087
+ def def_association_method(opts)
1088
+ association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
1064
1089
  end
1065
1090
 
1066
1091
  # Configures many_to_many association reflection and adds the related association methods