sequel 3.34.1 → 3.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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