sequel 5.39.0 → 5.72.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +408 -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 +13 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +26 -12
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- 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/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +28 -16
- data/doc/testing.rdoc +22 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
- data/lib/sequel/adapters/jdbc.rb +16 -18
- data/lib/sequel/adapters/mysql.rb +92 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +6 -2
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +83 -40
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +90 -9
- data/lib/sequel/adapters/shared/mysql.rb +47 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -1
- data/lib/sequel/adapters/shared/postgres.rb +496 -178
- data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
- data/lib/sequel/adapters/shared/sqlite.rb +116 -11
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -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 +55 -31
- data/lib/sequel/core.rb +28 -18
- data/lib/sequel/database/connecting.rb +27 -3
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +69 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +46 -53
- data/lib/sequel/database/schema_methods.rb +18 -2
- data/lib/sequel/dataset/actions.rb +108 -14
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +171 -44
- data/lib/sequel/dataset/sql.rb +182 -47
- 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 +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +439 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/blank.rb +8 -0
- 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 +71 -31
- 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 +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/inflector.rb +9 -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 +11 -2
- data/lib/sequel/extensions/named_timezones.rb +26 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +32 -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 +2 -3
- data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
- 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 +45 -19
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +73 -2
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +11 -24
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +21 -19
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/query.rb +2 -0
- 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/model/associations.rb +345 -101
- data/lib/sequel/model/base.rb +51 -27
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +87 -15
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +10 -4
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +10 -6
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- 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 +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +3 -1
- 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 +12 -7
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/pg_array_associations.rb +56 -38
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -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 +27 -19
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- 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 +46 -12
- 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 +132 -38
@@ -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
|
@@ -143,10 +143,14 @@ module Sequel
|
|
143
143
|
compositions[name] = send(composer_meth)
|
144
144
|
end
|
145
145
|
end
|
146
|
-
|
146
|
+
alias_method(name, name)
|
147
|
+
|
148
|
+
meth = :"#{name}="
|
149
|
+
define_method(meth) do |v|
|
147
150
|
modified!
|
148
151
|
compositions[name] = v
|
149
152
|
end
|
153
|
+
alias_method(meth, meth)
|
150
154
|
end
|
151
155
|
end
|
152
156
|
|
@@ -167,8 +171,10 @@ module Sequel
|
|
167
171
|
|
168
172
|
# Freeze compositions hash when freezing model instance.
|
169
173
|
def freeze
|
170
|
-
compositions
|
174
|
+
compositions
|
171
175
|
super
|
176
|
+
compositions.freeze
|
177
|
+
self
|
172
178
|
end
|
173
179
|
|
174
180
|
# For each composition, set the columns in the model class based
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
extension 'async_thread_pool'
|
5
|
+
|
6
|
+
module Plugins
|
7
|
+
# The concurrent_eager_loading plugin allows for eager loading multiple associations
|
8
|
+
# concurrently in separate threads. You must load the async_thread_pool Database
|
9
|
+
# extension into the Database object the model class uses in order for this plugin
|
10
|
+
# to work.
|
11
|
+
#
|
12
|
+
# By default in Sequel, eager loading happens in a serial manner. If you have code
|
13
|
+
# such as:
|
14
|
+
#
|
15
|
+
# Album.eager(:artist, :genre, :tracks)
|
16
|
+
#
|
17
|
+
# Sequel will load the albums, then the artists for the albums, then
|
18
|
+
# the genres for the albums, then the tracks for the albums.
|
19
|
+
#
|
20
|
+
# With the concurrent_eager_loading plugin, you can use the +eager_load_concurrently+
|
21
|
+
# method to allow for concurrent eager loading:
|
22
|
+
#
|
23
|
+
# Album.eager_load_concurrently.eager(:artist, :genre, :tracks)
|
24
|
+
#
|
25
|
+
# This will load the albums, first, since it needs to load the albums to know
|
26
|
+
# which artists, genres, and tracks to eagerly load. However, it will load the
|
27
|
+
# artists, genres, and tracks for the albums concurrently in separate threads.
|
28
|
+
# This can significantly improve performance, especially if there is significant
|
29
|
+
# latency between the application and the database. Note that using separate threads
|
30
|
+
# is only used in the case where there are multiple associations to eagerly load.
|
31
|
+
# With only a single association to eagerly load, there is no reason to use a
|
32
|
+
# separate thread, since it would not improve performance.
|
33
|
+
#
|
34
|
+
# If you want to make concurrent eager loading the default, you can load the
|
35
|
+
# plugin with the +:always+ option. In this case, all eager loads will be
|
36
|
+
# concurrent. If you want to force a non-concurrent eager load, you can use
|
37
|
+
# +eager_load_serially+:
|
38
|
+
#
|
39
|
+
# Album.eager_load_serially.eager(:artist, :genre, :tracks)
|
40
|
+
#
|
41
|
+
# Note that making concurrent eager loading the default is probably a bad idea
|
42
|
+
# if you are eager loading inside transactions and want the eager load to
|
43
|
+
# reflect changes made inside the transaction, unless you plan to use
|
44
|
+
# +eager_load_serially+ for such cases. See the async_thread_pool
|
45
|
+
# Database extension documentation for more general caveats regarding its use.
|
46
|
+
#
|
47
|
+
# The default eager loaders for all of the association types that ship with Sequel
|
48
|
+
# support safe concurrent eager loading. However, if you are specifying a custom
|
49
|
+
# +:eager_loader+ for an association, it may not work safely unless it it modified to
|
50
|
+
# support concurrent eager loading. Taking this example from the
|
51
|
+
# {Advanced Associations guide}[rdoc-ref:doc/advanced_associations.rdoc]
|
52
|
+
#
|
53
|
+
# Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
|
54
|
+
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
55
|
+
# id_map = eo_opts[:id_map]
|
56
|
+
# Artist.where(id: id_map.keys).all do |artist|
|
57
|
+
# if albums = id_map[artist.id]
|
58
|
+
# albums.each do |album|
|
59
|
+
# album.associations[:artist] = artist
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end)
|
64
|
+
#
|
65
|
+
# This would not support concurrent eager loading safely. To support safe
|
66
|
+
# concurrent eager loading, you need to make sure you are not modifying
|
67
|
+
# the associations for objects concurrently by separate threads. This is
|
68
|
+
# implemented using a mutex, which you can access via <tt>eo_opts[:mutex]</tt>.
|
69
|
+
# To keep things simple, you can use +Sequel.synchronize_with+ to only
|
70
|
+
# use this mutex if it is available. You want to use the mutex around the
|
71
|
+
# code that initializes the associations (usually to +nil+ or <tt>[]</tt>),
|
72
|
+
# and also around the code that sets the associatied objects appropriately
|
73
|
+
# after they have been retreived. You do not want to use the mutex around
|
74
|
+
# the code that loads the objects, since that will prevent concurrent loading.
|
75
|
+
# So after the changes, the custom eager loader would look like this:
|
76
|
+
#
|
77
|
+
# Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
|
78
|
+
# Sequel.synchronize_with(eo[:mutex]) do
|
79
|
+
# eo_opts[:rows].each{|album| album.associations[:artist] = nil}
|
80
|
+
# end
|
81
|
+
# id_map = eo_opts[:id_map]
|
82
|
+
# rows = Artist.where(id: id_map.keys).all
|
83
|
+
# Sequel.synchronize_with(eo[:mutex]) do
|
84
|
+
# rows.each do |artist|
|
85
|
+
# if albums = id_map[artist.id]
|
86
|
+
# albums.each do |album|
|
87
|
+
# album.associations[:artist] = artist
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# end)
|
93
|
+
#
|
94
|
+
# Usage:
|
95
|
+
#
|
96
|
+
# # Make all model subclass datasets support concurrent eager loading
|
97
|
+
# Sequel::Model.plugin :concurrent_eager_loading
|
98
|
+
#
|
99
|
+
# # Make the Album class datasets support concurrent eager loading
|
100
|
+
# Album.plugin :concurrent_eager_loading
|
101
|
+
#
|
102
|
+
# # Make all model subclass datasets concurrently eager load by default
|
103
|
+
# Sequel::Model.plugin :concurrent_eager_loading, always: true
|
104
|
+
module ConcurrentEagerLoading
|
105
|
+
def self.configure(mod, opts=OPTS)
|
106
|
+
if opts.has_key?(:always)
|
107
|
+
mod.instance_variable_set(:@always_eager_load_concurrently, opts[:always])
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module ClassMethods
|
112
|
+
Plugins.inherited_instance_variables(self, :@always_eager_load_concurrently => nil)
|
113
|
+
Plugins.def_dataset_methods(self, [:eager_load_concurrently, :eager_load_serially])
|
114
|
+
|
115
|
+
# Whether datasets for this class should eager load concurrently by default.
|
116
|
+
def always_eager_load_concurrently?
|
117
|
+
@always_eager_load_concurrently
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module DatasetMethods
|
122
|
+
# Return a cloned dataset that will eager load associated results concurrently
|
123
|
+
# using the async thread pool.
|
124
|
+
def eager_load_concurrently
|
125
|
+
cached_dataset(:_eager_load_concurrently) do
|
126
|
+
clone(:eager_load_concurrently=>true)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return a cloned dataset that will noteager load associated results concurrently
|
131
|
+
# using the async thread pool. Only useful if the current dataset has been marked
|
132
|
+
# as loading concurrently, or loading concurrently is the model's default behavior.
|
133
|
+
def eager_load_serially
|
134
|
+
cached_dataset(:_eager_load_serially) do
|
135
|
+
clone(:eager_load_concurrently=>false)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Whether this particular dataset will eager load results concurrently.
|
142
|
+
def eager_load_concurrently?
|
143
|
+
v = @opts[:eager_load_concurrently]
|
144
|
+
v.nil? ? model.always_eager_load_concurrently? : v
|
145
|
+
end
|
146
|
+
|
147
|
+
# If performing eager loads concurrently, and at least 2 associations are being
|
148
|
+
# eagerly loaded, create a single mutex used for all eager loads. After the
|
149
|
+
# eager loads have been performed, force loading of any async results, so that
|
150
|
+
# all eager loads will have been completed before this method returns.
|
151
|
+
def perform_eager_loads(eager_load_data)
|
152
|
+
return super if !eager_load_concurrently? || eager_load_data.length < 2
|
153
|
+
|
154
|
+
mutex = Mutex.new
|
155
|
+
eager_load_data.each_value do |eo|
|
156
|
+
eo[:mutex] = mutex
|
157
|
+
end
|
158
|
+
|
159
|
+
super.each do |v|
|
160
|
+
if Sequel::Database::AsyncThreadPool::BaseProxy === v
|
161
|
+
v.__value
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# If performing eager loads concurrently, perform this eager load using the
|
167
|
+
# async thread pool.
|
168
|
+
def perform_eager_load(loader, eo)
|
169
|
+
eo[:mutex] ? db.send(:async_run){super} : super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -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
|
@@ -220,7 +223,8 @@ module Sequel
|
|
220
223
|
'%'
|
221
224
|
when '%'
|
222
225
|
'.*'
|
223
|
-
|
226
|
+
else
|
227
|
+
#when '_'
|
224
228
|
'.'
|
225
229
|
end
|
226
230
|
end
|
@@ -62,7 +62,10 @@ module Sequel
|
|
62
62
|
ret = super
|
63
63
|
r = association_reflection(name)
|
64
64
|
meth = r.returns_array? ? name : pluralize(name).to_sym
|
65
|
-
dataset_module
|
65
|
+
dataset_module do
|
66
|
+
define_method(meth){associated(name)}
|
67
|
+
alias_method(meth, meth)
|
68
|
+
end
|
66
69
|
ret
|
67
70
|
end
|
68
71
|
|
@@ -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:
|
@@ -133,21 +133,39 @@ module Sequel
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
# work by creating instances of this class, which take a
|
139
|
-
# literal JSON string and have +to_json+ return it.
|
136
|
+
# SEQUEL6: Remove
|
137
|
+
# :nocov:
|
140
138
|
class Literal
|
141
|
-
# Store the literal JSON to use
|
142
139
|
def initialize(json)
|
143
140
|
@json = json
|
144
141
|
end
|
145
142
|
|
146
|
-
# Return the literal JSON to use
|
147
143
|
def to_json(*a)
|
148
144
|
@json
|
149
145
|
end
|
150
146
|
end
|
147
|
+
# :nocov:
|
148
|
+
Sequel::Deprecation.deprecate_constant(self, :Literal)
|
149
|
+
|
150
|
+
# Convert the given object to a JSON data structure using the given arguments.
|
151
|
+
def self.object_to_json_data(obj, *args, &block)
|
152
|
+
if obj.is_a?(Array)
|
153
|
+
obj.map{|x| object_to_json_data(x, *args, &block)}
|
154
|
+
else
|
155
|
+
if obj.respond_to?(:to_json_data)
|
156
|
+
obj.to_json_data(*args, &block)
|
157
|
+
else
|
158
|
+
begin
|
159
|
+
Sequel.parse_json(Sequel.object_to_json(obj, *args, &block))
|
160
|
+
# :nocov:
|
161
|
+
rescue Sequel.json_parser_error_class
|
162
|
+
# Support for old Ruby code that only supports parsing JSON object/array
|
163
|
+
Sequel.parse_json(Sequel.object_to_json([obj], *args, &block))[0]
|
164
|
+
# :nocov:
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
151
169
|
|
152
170
|
module ClassMethods
|
153
171
|
# The default opts to use when serializing model objects to JSON.
|
@@ -324,20 +342,7 @@ module Sequel
|
|
324
342
|
end
|
325
343
|
|
326
344
|
v = v.empty? ? [] : [v]
|
327
|
-
|
328
|
-
objs = public_send(k)
|
329
|
-
|
330
|
-
is_array = if r = model.association_reflection(k)
|
331
|
-
r.returns_array?
|
332
|
-
else
|
333
|
-
objs.is_a?(Array)
|
334
|
-
end
|
335
|
-
|
336
|
-
h[key_name] = if is_array
|
337
|
-
objs.map{|obj| Literal.new(Sequel.object_to_json(obj, *v))}
|
338
|
-
else
|
339
|
-
Literal.new(Sequel.object_to_json(objs, *v))
|
340
|
-
end
|
345
|
+
h[key_name] = JsonSerializer.object_to_json_data(public_send(k), *v)
|
341
346
|
end
|
342
347
|
else
|
343
348
|
Array(inc).each do |c|
|
@@ -347,7 +352,8 @@ module Sequel
|
|
347
352
|
else
|
348
353
|
key_name = c.to_s
|
349
354
|
end
|
350
|
-
|
355
|
+
|
356
|
+
h[key_name] = JsonSerializer.object_to_json_data(public_send(c))
|
351
357
|
end
|
352
358
|
end
|
353
359
|
end
|
@@ -359,9 +365,18 @@ module Sequel
|
|
359
365
|
h = {root => h}
|
360
366
|
end
|
361
367
|
|
362
|
-
h = yield h if
|
368
|
+
h = yield h if defined?(yield)
|
363
369
|
Sequel.object_to_json(h, *a)
|
364
370
|
end
|
371
|
+
|
372
|
+
# Convert the receiver to a JSON data structure using the given arguments.
|
373
|
+
def to_json_data(*args, &block)
|
374
|
+
if block
|
375
|
+
to_json(*args){|x| return block.call(x)}
|
376
|
+
else
|
377
|
+
to_json(*args){|x| return x}
|
378
|
+
end
|
379
|
+
end
|
365
380
|
end
|
366
381
|
|
367
382
|
module DatasetMethods
|
@@ -420,13 +435,13 @@ module Sequel
|
|
420
435
|
else
|
421
436
|
all
|
422
437
|
end
|
423
|
-
|
438
|
+
JsonSerializer.object_to_json_data(array, opts, &opts[:instance_block])
|
424
439
|
else
|
425
440
|
all
|
426
441
|
end
|
427
442
|
|
428
443
|
res = {collection_root => res} if collection_root
|
429
|
-
res = yield res if
|
444
|
+
res = yield res if defined?(yield)
|
430
445
|
|
431
446
|
Sequel.object_to_json(res, *a)
|
432
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:
|