sequel 5.45.0 → 5.77.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +434 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +59 -27
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +119 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +27 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +28 -12
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/release_notes/5.67.0.txt +32 -0
- data/doc/release_notes/5.68.0.txt +61 -0
- data/doc/release_notes/5.69.0.txt +26 -0
- data/doc/release_notes/5.70.0.txt +35 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/release_notes/5.73.0.txt +66 -0
- data/doc/release_notes/5.74.0.txt +45 -0
- data/doc/release_notes/5.75.0.txt +35 -0
- data/doc/release_notes/5.76.0.txt +86 -0
- data/doc/release_notes/5.77.0.txt +63 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +27 -15
- data/doc/testing.rdoc +23 -13
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +3 -3
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +63 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +8 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +24 -22
- data/lib/sequel/adapters/mysql.rb +92 -67
- data/lib/sequel/adapters/mysql2.rb +56 -51
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +89 -45
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +42 -0
- data/lib/sequel/adapters/shared/mssql.rb +91 -10
- data/lib/sequel/adapters/shared/mysql.rb +78 -3
- data/lib/sequel/adapters/shared/oracle.rb +86 -7
- data/lib/sequel/adapters/shared/postgres.rb +576 -171
- data/lib/sequel/adapters/shared/sqlanywhere.rb +21 -5
- data/lib/sequel/adapters/shared/sqlite.rb +92 -8
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +99 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/connection_pool/threaded.rb +14 -8
- data/lib/sequel/connection_pool/timed_queue.rb +270 -0
- data/lib/sequel/connection_pool.rb +57 -31
- data/lib/sequel/core.rb +17 -18
- data/lib/sequel/database/connecting.rb +27 -3
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +70 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +11 -6
- data/lib/sequel/database/schema_methods.rb +23 -4
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +111 -15
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +20 -1
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/query.rb +170 -41
- data/lib/sequel/dataset/sql.rb +190 -71
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +2 -2
- data/lib/sequel/extensions/async_thread_pool.rb +14 -13
- data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +16 -11
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +36 -8
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +11 -10
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/inflector.rb +1 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +57 -15
- data/lib/sequel/extensions/named_timezones.rb +22 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +33 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/pg_enum.rb +1 -2
- data/lib/sequel/extensions/pg_extended_date_support.rb +39 -28
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +11 -11
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +125 -2
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +13 -26
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +20 -19
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
- data/lib/sequel/extensions/round_timestamps.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +45 -11
- data/lib/sequel/extensions/server_block.rb +10 -13
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
- data/lib/sequel/model/associations.rb +286 -92
- data/lib/sequel/model/base.rb +53 -33
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/exceptions.rb +15 -3
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +74 -16
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +29 -8
- data/lib/sequel/plugins/composition.rb +3 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
- data/lib/sequel/plugins/constraint_validations.rb +8 -5
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +2 -2
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +8 -3
- data/lib/sequel/plugins/many_through_many.rb +109 -10
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/nested_attributes.rb +4 -4
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/paged_operations.rb +181 -0
- data/lib/sequel/plugins/pg_array_associations.rb +46 -34
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/prepared_statements.rb +12 -2
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +7 -4
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +1 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +39 -1
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +41 -11
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +109 -19
@@ -39,8 +39,8 @@ module Sequel
|
|
39
39
|
# also be implemented as:
|
40
40
|
#
|
41
41
|
# Album.composition :date,
|
42
|
-
# :
|
43
|
-
# :
|
42
|
+
# composer: proc{Date.new(year, month, day) if year || month || day},
|
43
|
+
# decomposer: (proc do
|
44
44
|
# if d = compositions[:date]
|
45
45
|
# self.year = d.year
|
46
46
|
# self.month = d.month
|
@@ -174,6 +174,7 @@ module Sequel
|
|
174
174
|
compositions
|
175
175
|
super
|
176
176
|
compositions.freeze
|
177
|
+
self
|
177
178
|
end
|
178
179
|
|
179
180
|
# For each composition, set the columns in the model class based
|
@@ -50,10 +50,10 @@ module Sequel
|
|
50
50
|
# support concurrent eager loading. Taking this example from the
|
51
51
|
# {Advanced Associations guide}[rdoc-ref:doc/advanced_associations.rdoc]
|
52
52
|
#
|
53
|
-
# Album.many_to_one :artist, :
|
53
|
+
# Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
|
54
54
|
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
55
55
|
# id_map = eo_opts[:id_map]
|
56
|
-
# Artist.where(:
|
56
|
+
# Artist.where(id: id_map.keys).all do |artist|
|
57
57
|
# if albums = id_map[artist.id]
|
58
58
|
# albums.each do |album|
|
59
59
|
# album.associations[:artist] = artist
|
@@ -74,12 +74,12 @@ module Sequel
|
|
74
74
|
# the code that loads the objects, since that will prevent concurrent loading.
|
75
75
|
# So after the changes, the custom eager loader would look like this:
|
76
76
|
#
|
77
|
-
# Album.many_to_one :artist, :
|
77
|
+
# Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
|
78
78
|
# Sequel.synchronize_with(eo[:mutex]) do
|
79
79
|
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
80
80
|
# end
|
81
81
|
# id_map = eo_opts[:id_map]
|
82
|
-
# rows = Artist.where(:
|
82
|
+
# rows = Artist.where(id: id_map.keys).all
|
83
83
|
# Sequel.synchronize_with(eo[:mutex]) do
|
84
84
|
# rows.each do |artist|
|
85
85
|
# if albums = id_map[artist.id]
|
@@ -125,14 +125,15 @@ module Sequel
|
|
125
125
|
ds = @dataset.with_quote_identifiers(false)
|
126
126
|
table_name = ds.literal(ds.first_source_table)
|
127
127
|
reflections = {}
|
128
|
-
|
128
|
+
allow_missing_columns = db_schema.select{|col, sch| sch[:allow_null] == false && nil != sch[:default]}.map(&:first)
|
129
|
+
@constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections, allow_missing_columns)}
|
129
130
|
@constraint_validation_reflections = reflections
|
130
131
|
end
|
131
132
|
end
|
132
133
|
|
133
134
|
# Given a specific database constraint validation metadata row hash, transform
|
134
135
|
# it in an validation method call array suitable for splatting to send.
|
135
|
-
def constraint_validation_array(r, reflections)
|
136
|
+
def constraint_validation_array(r, reflections, allow_missing_columns=EMPTY_ARRAY)
|
136
137
|
opts = {}
|
137
138
|
opts[:message] = r[:message] if r[:message]
|
138
139
|
opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
|
@@ -191,11 +192,13 @@ module Sequel
|
|
191
192
|
reflection_opts[:argument] = arg
|
192
193
|
end
|
193
194
|
|
194
|
-
|
195
|
-
|
196
|
-
|
195
|
+
opts[:from] = :values
|
196
|
+
if column.is_a?(Symbol) && allow_missing_columns.include?(column)
|
197
|
+
opts[:allow_missing] = true
|
197
198
|
end
|
198
199
|
|
200
|
+
a << column << opts
|
201
|
+
|
199
202
|
if column.is_a?(Array) && column.length == 1
|
200
203
|
column = column.first
|
201
204
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
+
require 'delegate'
|
4
|
+
|
3
5
|
module Sequel
|
4
6
|
module Plugins
|
5
7
|
# The defaults_setter plugin makes the column getter methods return the default
|
@@ -106,6 +108,20 @@ module Sequel
|
|
106
108
|
lambda{Date.today}
|
107
109
|
when Sequel::CURRENT_TIMESTAMP
|
108
110
|
lambda{dataset.current_datetime}
|
111
|
+
when Hash, Array
|
112
|
+
v = Marshal.dump(v).freeze
|
113
|
+
lambda{Marshal.load(v)}
|
114
|
+
when Delegator
|
115
|
+
# DelegateClass returns an anonymous case, which cannot be marshalled, so marshal the
|
116
|
+
# underlying object and create a new instance of the class with the unmarshalled object.
|
117
|
+
klass = v.class
|
118
|
+
case o = v.__getobj__
|
119
|
+
when Hash, Array
|
120
|
+
v = Marshal.dump(o).freeze
|
121
|
+
lambda{klass.new(Marshal.load(v))}
|
122
|
+
else
|
123
|
+
v
|
124
|
+
end
|
109
125
|
else
|
110
126
|
v
|
111
127
|
end
|
data/lib/sequel/plugins/dirty.rb
CHANGED
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The enum plugin allows for easily adding methods to modify the value of
|
6
|
+
# a column. It allows treating the column itself as an enum, returning a
|
7
|
+
# symbol for the related enum value. It also allows for setting up dataset
|
8
|
+
# methods to easily find records having or not having each enum value.
|
9
|
+
#
|
10
|
+
# After loading the plugin, you can call the +enum+ method to define the
|
11
|
+
# methods. The +enum+ method accepts a symbol for the underlying
|
12
|
+
# database column, and a hash with symbol keys for the enum values.
|
13
|
+
# For example, the following call:
|
14
|
+
#
|
15
|
+
# Album.enum :status_id, good: 1, bad: 2
|
16
|
+
#
|
17
|
+
# Will define the following instance methods:
|
18
|
+
#
|
19
|
+
# Album#good! :: Change +status_id+ to +1+ (does not save the receiver)
|
20
|
+
# Album#bad! :: Change +status_id+ to +2+ (does not save the receiver)
|
21
|
+
# Album#good? :: Return whether +status_id+ is +1+
|
22
|
+
# Album#bad? :: Return whether +status_id+ is +2+
|
23
|
+
#
|
24
|
+
# It will override the following instance methods:
|
25
|
+
#
|
26
|
+
# Album#status_id :: Return +:good+/+:bad+ instead of +1+/+2+ (other values returned as-is)
|
27
|
+
# Album#status_id= :: Allow calling with +:good+/+:bad+ to set +status_id+ to +1+/+2+ (other values,
|
28
|
+
# such as <tt>'good'</tt>/<tt>'bad'</tt> set as-is)
|
29
|
+
#
|
30
|
+
# If will define the following dataset methods:
|
31
|
+
#
|
32
|
+
# Album.dataset.good :: Return a dataset filtered to rows where +status_id+ is +1+
|
33
|
+
# Album.dataset.not_good :: Return a dataset filtered to rows where +status_id+ is not +1+
|
34
|
+
# Album.dataset.bad:: Return a dataset filtered to rows where +status_id+ is +2+
|
35
|
+
# Album.dataset.not_bad:: Return a dataset filtered to rows where +status_id+ is not +2+
|
36
|
+
#
|
37
|
+
# When calling +enum+, you can also provide the following options:
|
38
|
+
#
|
39
|
+
# :prefix :: Use a prefix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the prefix.
|
40
|
+
# For example, with <tt>prefix: 'status'</tt>, the instance methods defined above would be +status_good?+, +status_bad?+,
|
41
|
+
# +status_good!+, and +status_bad!+, and the dataset methods defined would be +status_good+, +status_not_good+, +status_bad+,
|
42
|
+
# and +status_not_bad+.
|
43
|
+
# :suffix :: Use a suffix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the suffix.
|
44
|
+
# For example, with <tt>suffix: 'status'</tt>, the instance methods defined above would be +good_status?+, +bad_status?+,
|
45
|
+
# +good_status!+, and +bad_status!+, and the dataset methods defined would be +good_status+, +not_good_status+, +bad_status+,
|
46
|
+
# and +not_bad_status+.
|
47
|
+
# :override_accessors :: Set to +false+ to not override the column accessor methods.
|
48
|
+
# :dataset_methods :: Set to +false+ to not define dataset methods.
|
49
|
+
#
|
50
|
+
# Note that this does not use a true enum column in the database. If you are
|
51
|
+
# looking for enum support in the database, and your are using PostgreSQL,
|
52
|
+
# Sequel supports that via the pg_enum Database extension.
|
53
|
+
#
|
54
|
+
# Usage:
|
55
|
+
#
|
56
|
+
# # Make all model subclasses handle enums
|
57
|
+
# Sequel::Model.plugin :enum
|
58
|
+
#
|
59
|
+
# # Make the Album class handle enums
|
60
|
+
# Album.plugin :enum
|
61
|
+
module Enum
|
62
|
+
module ClassMethods
|
63
|
+
# Define instance and dataset methods in this class to treat column
|
64
|
+
# as a enum. See Enum documentation for usage.
|
65
|
+
def enum(column, values, opts=OPTS)
|
66
|
+
raise Sequel::Error, "enum column must be a symbol" unless column.is_a?(Symbol)
|
67
|
+
raise Sequel::Error, "enum values must be provided as a hash with symbol keys" unless values.is_a?(Hash) && values.all?{|k,| k.is_a?(Symbol)}
|
68
|
+
|
69
|
+
if prefix = opts[:prefix]
|
70
|
+
prefix = column if prefix == true
|
71
|
+
prefix = "#{prefix}_"
|
72
|
+
end
|
73
|
+
|
74
|
+
if suffix = opts[:suffix]
|
75
|
+
suffix = column if suffix == true
|
76
|
+
suffix = "_#{suffix}"
|
77
|
+
end
|
78
|
+
|
79
|
+
values = Hash[values].freeze
|
80
|
+
inverted = values.invert.freeze
|
81
|
+
|
82
|
+
unless @enum_methods
|
83
|
+
@enum_methods = Module.new
|
84
|
+
include @enum_methods
|
85
|
+
end
|
86
|
+
|
87
|
+
@enum_methods.module_eval do
|
88
|
+
unless opts[:override_accessors] == false
|
89
|
+
define_method(column) do
|
90
|
+
v = super()
|
91
|
+
inverted.fetch(v, v)
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method(:"#{column}=") do |v|
|
95
|
+
super(values.fetch(v, v))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
values.each do |key, value|
|
100
|
+
define_method(:"#{prefix}#{key}#{suffix}!") do
|
101
|
+
self[column] = value
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
define_method(:"#{prefix}#{key}#{suffix}?") do
|
106
|
+
self[column] == value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
unless opts[:dataset_methods] == false
|
112
|
+
dataset_module do
|
113
|
+
values.each do |key, value|
|
114
|
+
cond = Sequel[column=>value]
|
115
|
+
where :"#{prefix}#{key}#{suffix}", cond
|
116
|
+
where :"#{prefix}not_#{key}#{suffix}", ~cond
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -97,7 +97,9 @@ module Sequel
|
|
97
97
|
# Artist.first_by_name(nil)
|
98
98
|
# # WHERE (name IS NULL)
|
99
99
|
#
|
100
|
-
# See Dataset::PlaceholderLiteralizer for additional caveats.
|
100
|
+
# See Dataset::PlaceholderLiteralizer for additional caveats. Note that if the model's
|
101
|
+
# dataset does not support placeholder literalizers, you will not be able to use this
|
102
|
+
# method.
|
101
103
|
def finder(meth=OPTS, opts=OPTS, &block)
|
102
104
|
if block
|
103
105
|
raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash)
|
@@ -149,7 +151,7 @@ module Sequel
|
|
149
151
|
ds
|
150
152
|
end
|
151
153
|
|
152
|
-
|
154
|
+
model.dataset.placeholder_literalizer_class.loader(model, &block)
|
153
155
|
end
|
154
156
|
end
|
155
157
|
|
@@ -22,6 +22,10 @@ module Sequel
|
|
22
22
|
# for that album. See the PostgreSQL and SQLite adapter documention for
|
23
23
|
# the options you can pass to the insert_conflict method.
|
24
24
|
#
|
25
|
+
# You should not attempt to use this plugin to ignore conflicts when
|
26
|
+
# inserting, you should only use it to turn insert conflicts into updates.
|
27
|
+
# Any usage to ignore conflicts is not recommended or supported.
|
28
|
+
#
|
25
29
|
# Usage:
|
26
30
|
#
|
27
31
|
# # Make all model subclasses support insert_conflict
|
@@ -29,7 +29,7 @@ module Sequel
|
|
29
29
|
# end
|
30
30
|
#
|
31
31
|
# +first_track+ is not instance specific, but +last_track+ and +recent_tracks+ are.
|
32
|
-
# +
|
32
|
+
# +last_track+ is because the +num_tracks+ call in the block is calling
|
33
33
|
# <tt>Album#num_tracks</tt>. +recent_tracks+ is because the value will change over
|
34
34
|
# time. This plugin allows you to find these cases, and set the :instance_specific
|
35
35
|
# option appropriately for them:
|
@@ -365,7 +365,7 @@ module Sequel
|
|
365
365
|
h = {root => h}
|
366
366
|
end
|
367
367
|
|
368
|
-
h = yield h if
|
368
|
+
h = yield h if defined?(yield)
|
369
369
|
Sequel.object_to_json(h, *a)
|
370
370
|
end
|
371
371
|
|
@@ -441,7 +441,7 @@ module Sequel
|
|
441
441
|
end
|
442
442
|
|
443
443
|
res = {collection_root => res} if collection_root
|
444
|
-
res = yield res if
|
444
|
+
res = yield res if defined?(yield)
|
445
445
|
|
446
446
|
Sequel.object_to_json(res, *a)
|
447
447
|
end
|
@@ -52,7 +52,9 @@ module Sequel
|
|
52
52
|
unless select = dataset.opts[:select]
|
53
53
|
select = dataset.columns.map{|c| Sequel.qualify(dataset.first_source, c)}
|
54
54
|
end
|
55
|
+
db_schema = @db_schema
|
55
56
|
set_dataset(dataset.select(*select.reject{|c| attrs.include?(dataset.send(:_hash_key_symbol, c))}))
|
57
|
+
@db_schema = db_schema
|
56
58
|
attrs.each{|a| define_lazy_attribute_getter(a)}
|
57
59
|
end
|
58
60
|
|
@@ -71,6 +73,7 @@ module Sequel
|
|
71
73
|
super()
|
72
74
|
end
|
73
75
|
end
|
76
|
+
alias_method(a, a)
|
74
77
|
end
|
75
78
|
end
|
76
79
|
end
|
data/lib/sequel/plugins/list.rb
CHANGED
@@ -33,7 +33,9 @@ module Sequel
|
|
33
33
|
# You can provide a <tt>:scope</tt> option to scope the list. This option
|
34
34
|
# can be a symbol or array of symbols specifying column name(s), or a proc
|
35
35
|
# that accepts a model instance and returns a dataset representing the list
|
36
|
-
# the object is in.
|
36
|
+
# the object is in. You will need to provide a <tt>:scope</tt> option if
|
37
|
+
# the model's dataset uses a subquery (such as when using the class_table_inheritance
|
38
|
+
# plugin).
|
37
39
|
#
|
38
40
|
# For example, if each item has a +user_id+ field, and you want every user
|
39
41
|
# to have their own list:
|
@@ -183,10 +185,13 @@ module Sequel
|
|
183
185
|
end
|
184
186
|
|
185
187
|
# Set the value of the position_field to the maximum value plus 1 unless the
|
186
|
-
# position field already has a value.
|
188
|
+
# position field already has a value. If the list is empty, the position will
|
189
|
+
# be set to the model's +top_of_list+ value.
|
187
190
|
def before_validation
|
188
191
|
unless get_column_value(position_field)
|
189
|
-
|
192
|
+
current_max = list_dataset.max(position_field)
|
193
|
+
value = current_max.nil? ? model.top_of_list : current_max.to_i + 1
|
194
|
+
set_column_value("#{position_field}=", value)
|
190
195
|
end
|
191
196
|
super
|
192
197
|
end
|
@@ -123,16 +123,25 @@ module Sequel
|
|
123
123
|
nil
|
124
124
|
end
|
125
125
|
|
126
|
+
# Whether a separate query should be used for each join table.
|
127
|
+
def separate_query_per_table?
|
128
|
+
self[:separate_query_per_table]
|
129
|
+
end
|
130
|
+
|
126
131
|
private
|
127
132
|
|
128
133
|
def _associated_dataset
|
129
134
|
ds = associated_class
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
135
|
+
if separate_query_per_table?
|
136
|
+
ds = ds.dataset
|
137
|
+
else
|
138
|
+
(reverse_edges + [final_reverse_edge]).each do |t|
|
139
|
+
h = {:qualify=>:deep}
|
140
|
+
if t[:alias] != t[:table]
|
141
|
+
h[:table_alias] = t[:alias]
|
142
|
+
end
|
143
|
+
ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), h)
|
134
144
|
end
|
135
|
-
ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), h)
|
136
145
|
end
|
137
146
|
ds
|
138
147
|
end
|
@@ -208,6 +217,7 @@ module Sequel
|
|
208
217
|
# :right (last array element) :: The key joining the table to the next table. Can use an
|
209
218
|
# array of symbols for a composite key association.
|
210
219
|
# If a hash is provided, the following keys are respected when using eager_graph:
|
220
|
+
# :db :: The Database containing the table. This changes lookup to use a separate query for each join table.
|
211
221
|
# :block :: A proc to use as the block argument to join.
|
212
222
|
# :conditions :: Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
|
213
223
|
# :join_type :: The join type to use for the join, defaults to :left_outer.
|
@@ -233,32 +243,121 @@ module Sequel
|
|
233
243
|
opts[:after_load].unshift(:array_uniq!)
|
234
244
|
end
|
235
245
|
opts[:cartesian_product_number] ||= one_through_many ? 0 : 2
|
236
|
-
|
246
|
+
separate_query_per_table = false
|
247
|
+
through = opts[:through] = opts[:through].map do |e|
|
237
248
|
case e
|
238
249
|
when Array
|
239
250
|
raise(Error, "array elements of the through option/argument for many_through_many associations must have at least three elements") unless e.length == 3
|
240
251
|
{:table=>e[0], :left=>e[1], :right=>e[2]}
|
241
252
|
when Hash
|
242
253
|
raise(Error, "hash elements of the through option/argument for many_through_many associations must contain :table, :left, and :right keys") unless e[:table] && e[:left] && e[:right]
|
254
|
+
separate_query_per_table = true if e[:db]
|
243
255
|
e
|
244
256
|
else
|
245
257
|
raise(Error, "the through option/argument for many_through_many associations must be an enumerable of arrays or hashes")
|
246
258
|
end
|
247
259
|
end
|
260
|
+
opts[:separate_query_per_table] = separate_query_per_table
|
248
261
|
|
249
262
|
left_key = opts[:left_key] = opts[:through].first[:left]
|
250
263
|
opts[:left_keys] = Array(left_key)
|
251
|
-
opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
264
|
+
uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
252
265
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
253
266
|
raise(Error, "no primary key specified for #{inspect}") unless left_pk
|
254
267
|
opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
|
255
268
|
opts[:left_primary_keys] = Array(left_pk)
|
256
269
|
lpkc = opts[:left_primary_key_column] ||= left_pk
|
257
270
|
lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
|
258
|
-
opts[:dataset] ||= opts.association_dataset_proc
|
259
271
|
|
260
272
|
opts[:left_key_alias] ||= opts.default_associated_key_alias
|
261
|
-
|
273
|
+
if separate_query_per_table
|
274
|
+
opts[:use_placeholder_loader] = false
|
275
|
+
opts[:allow_eager_graph] = false
|
276
|
+
opts[:allow_filtering_by] = false
|
277
|
+
opts[:eager_limit_strategy] = nil
|
278
|
+
|
279
|
+
opts[:dataset] ||= proc do |r|
|
280
|
+
def_db = r.associated_class.db
|
281
|
+
vals = uses_lcks ? [lpkcs.map{|k| get_column_value(k)}] : get_column_value(left_pk)
|
282
|
+
|
283
|
+
has_results = through.each do |edge|
|
284
|
+
ds = (edge[:db] || def_db).from(edge[:table]).where(edge[:left]=>vals)
|
285
|
+
ds = ds.where(edge[:conditions]) if edge[:conditions]
|
286
|
+
right = edge[:right]
|
287
|
+
vals = ds.select_map(right)
|
288
|
+
if right.is_a?(Array)
|
289
|
+
vals.delete_if{|v| v.any?(&:nil?)}
|
290
|
+
else
|
291
|
+
vals.delete(nil)
|
292
|
+
end
|
293
|
+
break if vals.empty?
|
294
|
+
end
|
295
|
+
|
296
|
+
ds = r.associated_dataset.where(opts.right_primary_key=>vals)
|
297
|
+
ds = ds.clone(:no_results=>true) unless has_results
|
298
|
+
ds
|
299
|
+
end
|
300
|
+
opts[:eager_loader] ||= proc do |eo|
|
301
|
+
h = eo[:id_map]
|
302
|
+
assign_singular = opts.assign_singular?
|
303
|
+
uses_rcks = opts.right_primary_key.is_a?(Array)
|
304
|
+
rpk = uses_rcks ? opts.right_primary_keys : opts.right_primary_key
|
305
|
+
name = opts[:name]
|
306
|
+
def_db = opts.associated_class.db
|
307
|
+
join_map = h
|
308
|
+
|
309
|
+
run_query = through.each do |edge|
|
310
|
+
ds = (edge[:db] || def_db).from(edge[:table])
|
311
|
+
ds = ds.where(edge[:conditions]) if edge[:conditions]
|
312
|
+
left = edge[:left]
|
313
|
+
right = edge[:right]
|
314
|
+
prev_map = join_map
|
315
|
+
join_map = ds.where(left=>join_map.keys).select_hash_groups(right, left)
|
316
|
+
if right.is_a?(Array)
|
317
|
+
join_map.delete_if{|v,| v.any?(&:nil?)}
|
318
|
+
else
|
319
|
+
join_map.delete(nil)
|
320
|
+
end
|
321
|
+
break if join_map.empty?
|
322
|
+
join_map.each_value do |vs|
|
323
|
+
vs.replace(vs.flat_map{|v| prev_map[v]})
|
324
|
+
vs.uniq!
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
eo = Hash[eo]
|
329
|
+
|
330
|
+
if run_query
|
331
|
+
eo[:loader] = false
|
332
|
+
eo[:right_keys] = join_map.keys
|
333
|
+
else
|
334
|
+
eo[:no_results] = true
|
335
|
+
end
|
336
|
+
|
337
|
+
opts[:model].eager_load_results(opts, eo) do |assoc_record|
|
338
|
+
rpkv = if uses_rcks
|
339
|
+
assoc_record.values.values_at(*rpk)
|
340
|
+
else
|
341
|
+
assoc_record.values[rpk]
|
342
|
+
end
|
343
|
+
|
344
|
+
objects = join_map[rpkv]
|
345
|
+
|
346
|
+
if assign_singular
|
347
|
+
objects.each do |object|
|
348
|
+
object.associations[name] ||= assoc_record
|
349
|
+
end
|
350
|
+
else
|
351
|
+
objects.each do |object|
|
352
|
+
object.associations[name].push(assoc_record)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
else
|
358
|
+
opts[:dataset] ||= opts.association_dataset_proc
|
359
|
+
opts[:eager_loader] ||= opts.method(:default_eager_loader)
|
360
|
+
end
|
262
361
|
|
263
362
|
join_type = opts[:graph_join_type]
|
264
363
|
select = opts[:graph_select]
|
@@ -286,7 +385,7 @@ module Sequel
|
|
286
385
|
iq = nil
|
287
386
|
end
|
288
387
|
fe = opts.final_edge
|
289
|
-
ds.graph(opts
|
388
|
+
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
|
290
389
|
end
|
291
390
|
end
|
292
391
|
end
|
@@ -26,57 +26,27 @@ module Sequel
|
|
26
26
|
module MssqlOptimisticLocking
|
27
27
|
# Load the instance_filters plugin into the model.
|
28
28
|
def self.apply(model, opts=OPTS)
|
29
|
-
model.plugin
|
29
|
+
model.plugin(:optimistic_locking_base)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Set the
|
32
|
+
# Set the lock column
|
33
33
|
def self.configure(model, opts=OPTS)
|
34
|
-
model.lock_column = opts[:lock_column] || :timestamp
|
34
|
+
model.lock_column = opts[:lock_column] || model.lock_column || :timestamp
|
35
35
|
end
|
36
|
-
|
37
|
-
module ClassMethods
|
38
|
-
# The timestamp/rowversion column containing the version for the current row.
|
39
|
-
attr_accessor :lock_column
|
40
|
-
|
41
|
-
Plugins.inherited_instance_variables(self, :@lock_column=>nil)
|
42
|
-
end
|
43
|
-
|
36
|
+
|
44
37
|
module InstanceMethods
|
45
|
-
# Add the lock column instance filter to the object before destroying it.
|
46
|
-
def before_destroy
|
47
|
-
lock_column_instance_filter
|
48
|
-
super
|
49
|
-
end
|
50
|
-
|
51
|
-
# Add the lock column instance filter to the object before updating it.
|
52
|
-
def before_update
|
53
|
-
lock_column_instance_filter
|
54
|
-
super
|
55
|
-
end
|
56
|
-
|
57
38
|
private
|
58
39
|
|
59
|
-
#
|
60
|
-
def
|
61
|
-
|
62
|
-
instance_filter(lc=>Sequel.blob(get_column_value(lc)))
|
63
|
-
end
|
64
|
-
|
65
|
-
# Clear the instance filters when refreshing, so that attempting to
|
66
|
-
# refresh after a failed save removes the previous lock column filter
|
67
|
-
# (the new one will be added before updating).
|
68
|
-
def _refresh(ds)
|
69
|
-
clear_instance_filters
|
70
|
-
super
|
40
|
+
# Make the instance filter value a blob.
|
41
|
+
def lock_column_instance_filter_value
|
42
|
+
Sequel.blob(super)
|
71
43
|
end
|
72
44
|
|
73
45
|
# Remove the lock column from the columns to update.
|
74
46
|
# SQL Server automatically updates the lock column value, and does not like
|
75
47
|
# it to be assigned.
|
76
48
|
def _save_update_all_columns_hash
|
77
|
-
v =
|
78
|
-
cc = changed_columns
|
79
|
-
Array(primary_key).each{|x| v.delete(x) unless cc.include?(x)}
|
49
|
+
v = super
|
80
50
|
v.delete(model.lock_column)
|
81
51
|
v
|
82
52
|
end
|
@@ -33,7 +33,7 @@ module Sequel
|
|
33
33
|
# objects. You just need to make sure that the primary key field is filled in for the
|
34
34
|
# associated object:
|
35
35
|
#
|
36
|
-
# a.update(:
|
36
|
+
# a.update(albums_attributes: [{id: 1, name: 'T'}])
|
37
37
|
#
|
38
38
|
# Since the primary key field is filled in, the plugin will update the album with id 1 instead
|
39
39
|
# of creating a new album.
|
@@ -42,14 +42,14 @@ module Sequel
|
|
42
42
|
# entry to the hash, and also pass the :destroy option when calling +nested_attributes+:
|
43
43
|
#
|
44
44
|
# Artist.nested_attributes :albums, destroy: true
|
45
|
-
# a.update(:
|
45
|
+
# a.update(albums_attributes: [{id: 1, _delete: true}])
|
46
46
|
#
|
47
47
|
# This will delete the related associated object from the database. If you want to leave the
|
48
48
|
# associated object in the database, but just remove it from the association, add a _remove
|
49
49
|
# entry in the hash, and also pass the :remove option when calling +nested_attributes+:
|
50
50
|
#
|
51
51
|
# Artist.nested_attributes :albums, remove: true
|
52
|
-
# a.update(:
|
52
|
+
# a.update(albums_attributes: [{id: 1, _remove: true}])
|
53
53
|
#
|
54
54
|
# The above example was for a one_to_many association, but the plugin also works similarly
|
55
55
|
# for other association types. For one_to_one and many_to_one associations, you need to
|
@@ -84,7 +84,7 @@ module Sequel
|
|
84
84
|
# nested attributes options for that association. This is useful for per-call filtering
|
85
85
|
# of the allowed fields:
|
86
86
|
#
|
87
|
-
# a.set_nested_attributes(:albums, params['artist'], :
|
87
|
+
# a.set_nested_attributes(:albums, params['artist'], fields: %w'name')
|
88
88
|
module NestedAttributes
|
89
89
|
# Depend on the validate_associated plugin.
|
90
90
|
def self.apply(model)
|