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.
- 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
|