sequel 3.34.1 → 3.35.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +52 -0
- data/README.rdoc +3 -1
- data/Rakefile +2 -10
- data/doc/active_record.rdoc +1 -0
- data/doc/migration.rdoc +18 -7
- data/doc/model_hooks.rdoc +6 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/prepared_statements.rdoc +0 -1
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/schema_modification.rdoc +16 -1
- data/doc/thread_safety.rdoc +17 -0
- data/lib/sequel/adapters/do.rb +2 -2
- data/lib/sequel/adapters/do/postgres.rb +1 -52
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/firebird.rb +1 -1
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc.rb +23 -19
- data/lib/sequel/adapters/jdbc/db2.rb +0 -5
- data/lib/sequel/adapters/jdbc/derby.rb +29 -2
- data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/informix.rb +0 -5
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -35
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
- data/lib/sequel/adapters/jdbc/transactions.rb +4 -4
- data/lib/sequel/adapters/mysql2.rb +1 -1
- data/lib/sequel/adapters/odbc.rb +3 -3
- data/lib/sequel/adapters/odbc/mssql.rb +14 -1
- data/lib/sequel/adapters/oracle.rb +6 -18
- data/lib/sequel/adapters/postgres.rb +36 -53
- data/lib/sequel/adapters/shared/db2.rb +16 -2
- data/lib/sequel/adapters/shared/mssql.rb +40 -9
- data/lib/sequel/adapters/shared/mysql.rb +16 -4
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
- data/lib/sequel/adapters/shared/oracle.rb +2 -0
- data/lib/sequel/adapters/shared/postgres.rb +135 -211
- data/lib/sequel/adapters/sqlite.rb +2 -2
- data/lib/sequel/adapters/swift.rb +1 -1
- data/lib/sequel/adapters/swift/postgres.rb +1 -71
- data/lib/sequel/adapters/tinytds.rb +3 -3
- data/lib/sequel/core.rb +27 -4
- data/lib/sequel/database/connecting.rb +7 -8
- data/lib/sequel/database/logging.rb +6 -1
- data/lib/sequel/database/misc.rb +20 -4
- data/lib/sequel/database/query.rb +38 -18
- data/lib/sequel/database/schema_generator.rb +5 -2
- data/lib/sequel/database/schema_methods.rb +34 -8
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/sql.rb +18 -24
- data/lib/sequel/extensions/core_extensions.rb +0 -23
- data/lib/sequel/extensions/migration.rb +22 -8
- data/lib/sequel/extensions/pg_auto_parameterize.rb +4 -0
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/model.rb +2 -2
- data/lib/sequel/model/associations.rb +95 -70
- data/lib/sequel/model/base.rb +16 -18
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/identity_map.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +16 -1
- data/lib/sequel/plugins/many_through_many.rb +22 -32
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +2 -2
- data/lib/sequel/plugins/prepared_statements.rb +22 -8
- data/lib/sequel/plugins/prepared_statements_associations.rb +2 -3
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +10 -2
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +12 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/postgres_spec.rb +30 -79
- data/spec/core/database_spec.rb +46 -2
- data/spec/core/dataset_spec.rb +28 -22
- data/spec/core/schema_generator_spec.rb +1 -1
- data/spec/core/schema_spec.rb +51 -0
- data/spec/extensions/arbitrary_servers_spec.rb +0 -4
- data/spec/extensions/association_autoreloading_spec.rb +17 -0
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/core_extensions_spec.rb +1 -24
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/json_serializer_spec.rb +13 -0
- data/spec/extensions/migration_spec.rb +28 -15
- data/spec/extensions/named_timezones_spec.rb +6 -8
- data/spec/extensions/pg_auto_parameterize_spec.rb +6 -5
- data/spec/extensions/schema_dumper_spec.rb +3 -1
- data/spec/extensions/xml_serializer_spec.rb +13 -0
- data/spec/files/{transactionless_migrations → transaction_specified_migrations}/001_create_alt_basic.rb +1 -1
- data/spec/files/{transactionless_migrations → transaction_specified_migrations}/002_create_basic.rb +0 -0
- data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/001_create_alt_basic.rb +0 -0
- data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/002_create_basic.rb +0 -0
- data/spec/integration/associations_test.rb +5 -7
- data/spec/integration/dataset_test.rb +25 -7
- data/spec/integration/plugin_test.rb +1 -1
- data/spec/integration/schema_test.rb +16 -1
- data/spec/model/associations_spec.rb +2 -2
- metadata +14 -9
- 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
|
-
|
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
|
-
# :
|
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 {"
|
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
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
"
|
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.
|
250
|
+
db.set_prepared_statement(name, ps) if name
|
251
251
|
ps
|
252
252
|
end
|
253
253
|
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -152,8 +152,8 @@ module Sequel
|
|
152
152
|
if opts[:sql]
|
153
153
|
static_sql(opts[:sql])
|
154
154
|
else
|
155
|
-
|
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
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
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
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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(
|
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
|
-
#
|
47
|
+
# Don't allow transaction overriding in old migrations.
|
48
48
|
def self.use_transactions
|
49
|
-
|
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,
|
77
|
+
# Whether to use transactions for this migration, default depends on the
|
78
|
+
# database.
|
78
79
|
attr_accessor :use_transactions
|
79
80
|
|
80
|
-
#
|
81
|
+
# Don't set transaction use by default.
|
81
82
|
def initialize
|
82
|
-
@use_transactions =
|
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
|
418
|
-
|
419
|
-
|
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
|
@@ -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
|
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
|
-
|
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
|
-
|
85
|
-
|
86
|
-
case s =
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
391
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
500
|
-
split_join_table_alias
|
501
|
-
|
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
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
1051
|
-
|
1052
|
-
|
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
|