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
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The primary_key_lookup_check_values plugin typecasts given primary key
|
6
|
+
# values before performing a lookup by primary key. If the given primary
|
7
|
+
# key value cannot be typecasted correctly, the lookup returns nil
|
8
|
+
# without issuing a query. If the schema for the primary key column
|
9
|
+
# includes minimum and maximum values, this also checks the given value
|
10
|
+
# is not outside the range. If the given value is outside the allowed
|
11
|
+
# range, the lookup returns nil without issuing a query.
|
12
|
+
#
|
13
|
+
# This affects the following Model methods:
|
14
|
+
#
|
15
|
+
# * Model.[] (when called with non-Hash)
|
16
|
+
# * Model.with_pk
|
17
|
+
# * Model.with_pk!
|
18
|
+
#
|
19
|
+
# It also affects the following Model dataset methods:
|
20
|
+
#
|
21
|
+
# * Dataset#[] (when called with Integer)
|
22
|
+
# * Dataset#with_pk
|
23
|
+
# * dataset#with_pk!
|
24
|
+
#
|
25
|
+
# Note that this can break working code. The above methods accept
|
26
|
+
# any filter condition by default, not just primary key values. The
|
27
|
+
# plugin will handle Symbol, Sequel::SQL::Expression, and
|
28
|
+
# Sequel::LiteralString objects, but code such as the following will break:
|
29
|
+
#
|
30
|
+
# # Return first Album where primary key is one of the given values
|
31
|
+
# Album.dataset.with_pk([1, 2, 3])
|
32
|
+
#
|
33
|
+
# Usage:
|
34
|
+
#
|
35
|
+
# # Make all model subclasses support checking primary key values before
|
36
|
+
# # lookup # (called before loading subclasses)
|
37
|
+
# Sequel::Model.plugin :primary_key_lookup_check_values
|
38
|
+
#
|
39
|
+
# # Make the Album class support checking primary key values before lookup
|
40
|
+
# Album.plugin :primary_key_lookup_check_values
|
41
|
+
module PrimaryKeyLookupCheckValues
|
42
|
+
def self.configure(model)
|
43
|
+
model.instance_exec do
|
44
|
+
setup_primary_key_lookup_check_values if @dataset
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
Plugins.after_set_dataset(self, :setup_primary_key_lookup_check_values)
|
50
|
+
|
51
|
+
Plugins.inherited_instance_variables(self,
|
52
|
+
:@primary_key_type=>nil,
|
53
|
+
:@primary_key_value_range=>nil)
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Check the given primary key value. Typecast it to the appropriate
|
58
|
+
# database type if the database type is known. If it cannot be
|
59
|
+
# typecasted, or the typecasted value is outside the range of column
|
60
|
+
# values, return nil.
|
61
|
+
def _check_pk_lookup_value(pk)
|
62
|
+
return if nil == pk
|
63
|
+
case pk
|
64
|
+
when SQL::Expression, LiteralString, Symbol
|
65
|
+
return pk
|
66
|
+
end
|
67
|
+
return pk unless pk_type = @primary_key_type
|
68
|
+
|
69
|
+
if pk_type.is_a?(Array)
|
70
|
+
return unless pk.is_a?(Array)
|
71
|
+
return unless pk.size == pk_type.size
|
72
|
+
return if pk.any?(&:nil?)
|
73
|
+
|
74
|
+
pk_value_range = @primary_key_value_range
|
75
|
+
i = 0
|
76
|
+
pk.map do |v|
|
77
|
+
if type = pk_type[i]
|
78
|
+
v = _typecast_pk_lookup_value(v, type)
|
79
|
+
return if nil == v
|
80
|
+
if pk_value_range
|
81
|
+
min, max = pk_value_range[i]
|
82
|
+
return if min && v < min
|
83
|
+
return if max && v > max
|
84
|
+
end
|
85
|
+
end
|
86
|
+
i += 1
|
87
|
+
v
|
88
|
+
end
|
89
|
+
elsif pk.is_a?(Array)
|
90
|
+
return
|
91
|
+
elsif nil != (pk = _typecast_pk_lookup_value(pk, pk_type))
|
92
|
+
min, max = @primary_key_value_range
|
93
|
+
return if min && pk < min
|
94
|
+
return if max && pk > max
|
95
|
+
pk
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Typecast the value to the appropriate type,
|
100
|
+
# returning nil if it cannot be typecasted.
|
101
|
+
def _typecast_pk_lookup_value(value, type)
|
102
|
+
db.typecast_value(type, value)
|
103
|
+
rescue InvalidValue
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Skip the primary key lookup if the typecasted and checked
|
108
|
+
# primary key value is nil.
|
109
|
+
def primary_key_lookup(pk)
|
110
|
+
unless nil == (pk = _check_pk_lookup_value(pk))
|
111
|
+
super
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Setup the primary key type and value range used for checking
|
116
|
+
# primary key values during lookup.
|
117
|
+
def setup_primary_key_lookup_check_values
|
118
|
+
if primary_key.is_a?(Array)
|
119
|
+
types = []
|
120
|
+
value_ranges = []
|
121
|
+
primary_key.each do |pk|
|
122
|
+
type, min, max = _type_min_max_values_for_column(pk)
|
123
|
+
types << type
|
124
|
+
value_ranges << ([min, max].freeze if min || max)
|
125
|
+
end
|
126
|
+
@primary_key_type = (types.freeze if types.any?)
|
127
|
+
@primary_key_value_range = (value_ranges.freeze if @primary_key_type && value_ranges.any?)
|
128
|
+
else
|
129
|
+
@primary_key_type, min, max = _type_min_max_values_for_column(primary_key)
|
130
|
+
@primary_key_value_range = ([min, max].freeze if @primary_key_type && (min || max))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return the type, min_value, and max_value schema entries
|
135
|
+
# for the column, if they exist.
|
136
|
+
def _type_min_max_values_for_column(column)
|
137
|
+
if schema = db_schema[column]
|
138
|
+
schema.values_at(:type, :min_value, :max_value)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
module DatasetMethods
|
144
|
+
# Skip the primary key lookup if the typecasted and checked
|
145
|
+
# primary key value is nil.
|
146
|
+
def with_pk(pk)
|
147
|
+
unless nil == (pk = model.send(:_check_pk_lookup_value, pk))
|
148
|
+
super
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -71,6 +71,8 @@ module Sequel
|
|
71
71
|
# (default: :t)
|
72
72
|
# :level_alias :: The symbol identifier to use when eagerly loading descendants
|
73
73
|
# up to a given level (default: :x_level_x)
|
74
|
+
# :union_all :: Whether to use UNION ALL or UNION with the recursive
|
75
|
+
# common table expression (default: true)
|
74
76
|
module RcteTree
|
75
77
|
# Create the appropriate parent, children, ancestors, and descendants
|
76
78
|
# associations for the model.
|
@@ -80,6 +82,7 @@ module Sequel
|
|
80
82
|
opts = opts.dup
|
81
83
|
opts[:class] = model
|
82
84
|
opts[:methods_module] = Module.new
|
85
|
+
opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
|
83
86
|
model.send(:include, opts[:methods_module])
|
84
87
|
|
85
88
|
key = opts[:key] ||= :parent_id
|
@@ -142,7 +145,7 @@ module Sequel
|
|
142
145
|
model.from(SQL::AliasedExpression.new(t, table_alias)).
|
143
146
|
with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
|
144
147
|
recursive_ds.select(*c_all),
|
145
|
-
:args=>col_aliases)
|
148
|
+
:args=>col_aliases, union_all: opts[:union_all])
|
146
149
|
end
|
147
150
|
aal = Array(a[:after_load])
|
148
151
|
aal << proc do |m, ancs|
|
@@ -191,7 +194,7 @@ module Sequel
|
|
191
194
|
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
192
195
|
ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
|
193
196
|
with_recursive(t, base_case, recursive_case,
|
194
|
-
:args=>((key_aliases + col_aliases) if col_aliases))
|
197
|
+
:args=>((key_aliases + col_aliases) if col_aliases), union_all: opts[:union_all])
|
195
198
|
ds = r.apply_eager_dataset_changes(ds)
|
196
199
|
ds = ds.select_append(ka) unless ds.opts[:select] == nil
|
197
200
|
model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil)) do |obj|
|
@@ -240,7 +243,7 @@ module Sequel
|
|
240
243
|
model.from(SQL::AliasedExpression.new(t, table_alias)).
|
241
244
|
with_recursive(t, col_aliases ? base_ds.select(*col_aliases) : base_ds.select_all,
|
242
245
|
recursive_ds.select(*c_all),
|
243
|
-
:args=>col_aliases)
|
246
|
+
:args=>col_aliases, union_all: opts[:union_all])
|
244
247
|
end
|
245
248
|
dal = Array(d[:after_load])
|
246
249
|
dal << proc do |m, descs|
|
@@ -299,7 +302,7 @@ module Sequel
|
|
299
302
|
table_alias = model.dataset.schema_and_table(model.table_name)[1].to_sym
|
300
303
|
ds = model.from(SQL::AliasedExpression.new(t, table_alias)).
|
301
304
|
with_recursive(t, base_case, recursive_case,
|
302
|
-
:args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases))
|
305
|
+
:args=>((key_aliases + col_aliases + (level ? [la] : [])) if col_aliases), union_all: opts[:union_all])
|
303
306
|
ds = r.apply_eager_dataset_changes(ds)
|
304
307
|
ds = ds.select_append(ka) unless ds.opts[:select] == nil
|
305
308
|
model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil, :associations=>OPTS)) do |obj|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The require_valid_schema plugin makes Sequel raise or warn if attempting
|
6
|
+
# to set the dataset of a model class to a simple table, where the database
|
7
|
+
# supports schema parsing, but schema parsing does not work for the model's
|
8
|
+
# table.
|
9
|
+
#
|
10
|
+
# The plugin's default behavior requires that all models that select from a
|
11
|
+
# single identifier have a valid table schema, if the database supports
|
12
|
+
# schema parsing. If the schema cannot be determined for such
|
13
|
+
# a model, an error is raised:
|
14
|
+
#
|
15
|
+
# Sequel::Model.plugin :require_valid_schema
|
16
|
+
#
|
17
|
+
# If you load the plugin with an argument of :warn, Sequel will warn instead
|
18
|
+
# of raising for such tables:
|
19
|
+
#
|
20
|
+
# Sequel::Model.plugin :require_valid_schema, :warn
|
21
|
+
#
|
22
|
+
# This can catch bugs where you expect models to have valid schema, but
|
23
|
+
# they do not. This setting only affects future attempts to set datasets
|
24
|
+
# in the current class and subclasses created in the future.
|
25
|
+
#
|
26
|
+
# If you load the plugin with an argument of false, it will not require valid schema.
|
27
|
+
# This can be used in subclasses where you do not want to require valid schema,
|
28
|
+
# but the plugin must be loaded before a dataset with invalid schema is set:
|
29
|
+
#
|
30
|
+
# Sequel::Model.plugin :require_valid_schema
|
31
|
+
# InvalidSchemaAllowed = Class.new(Sequel::Model)
|
32
|
+
# InvalidSchemaAllowed.plugin :require_valid_schema, false
|
33
|
+
# class MyModel < InvalidSchemaAllowed
|
34
|
+
# end
|
35
|
+
module RequireValidSchema
|
36
|
+
# Modify the current model's dataset selection, if the model
|
37
|
+
# has a dataset.
|
38
|
+
def self.configure(model, setting=true)
|
39
|
+
model.instance_variable_set(:@require_valid_schema, setting)
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
Plugins.inherited_instance_variables(self, :@require_valid_schema=>nil)
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# If the schema cannot be determined, the model uses a simple table,
|
48
|
+
# require_valid_schema is set, and the database supports schema parsing, raise or
|
49
|
+
# warn based on the require_valid_schema setting.
|
50
|
+
def get_db_schema_array(reload)
|
51
|
+
schema_array = super
|
52
|
+
|
53
|
+
if !schema_array && simple_table && @require_valid_schema
|
54
|
+
message = "Not able to parse schema for model: #{inspect}, table: #{simple_table}"
|
55
|
+
if @require_valid_schema == :warn
|
56
|
+
warn message
|
57
|
+
else
|
58
|
+
raise Error, message
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
schema_array
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -253,6 +253,14 @@ module Sequel
|
|
253
253
|
|
254
254
|
private
|
255
255
|
|
256
|
+
# Limit tactical eager loading objects to objects that support the same association.
|
257
|
+
def _filter_tactical_eager_load_objects(opts)
|
258
|
+
objects = defined?(super) ? super : retrieved_with.dup
|
259
|
+
name = opts[:name]
|
260
|
+
objects.select!{|x| x.model.association_reflections.include?(name)}
|
261
|
+
objects
|
262
|
+
end
|
263
|
+
|
256
264
|
# Don't allow use of prepared statements.
|
257
265
|
def use_prepared_statements_for?(type)
|
258
266
|
false
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The sql_comments plugin will automatically use SQL comments on
|
6
|
+
# queries for the model it is loaded into. These comments will
|
7
|
+
# show the related model, what type of method was called, and
|
8
|
+
# the method name (or association name for queries to load
|
9
|
+
# associations):
|
10
|
+
#
|
11
|
+
# album = Album[1]
|
12
|
+
# # SELECT * FROM albums WHERE (id = 1) LIMIT 1
|
13
|
+
# # -- model:Album,method_type:class,method:[]
|
14
|
+
#
|
15
|
+
# album.update(name: 'A')
|
16
|
+
# # UPDATE albums SET name = 'baz' WHERE (id = 1)
|
17
|
+
# # -- model:Album,method_type:instance,method:update
|
18
|
+
#
|
19
|
+
# album.artist
|
20
|
+
# # SELECT * FROM artists WHERE (artists.id = 1)
|
21
|
+
# # -- model:Album,method_type:association_load,association:artist
|
22
|
+
#
|
23
|
+
# Album.eager(:artists).all
|
24
|
+
# # SELECT * FROM albums
|
25
|
+
# # SELECT * FROM artists WHERE (artists.id IN (1))
|
26
|
+
# # -- model:Album,method_type:association_eager_load,association:artist
|
27
|
+
#
|
28
|
+
# Album.where(id: 1).delete
|
29
|
+
# # DELETE FROM albums WHERE (id = 1)
|
30
|
+
# # -- model:Album,method_type:dataset,method:delete
|
31
|
+
#
|
32
|
+
# This plugin automatically supports the class, instance, and dataset
|
33
|
+
# methods are are supported by default in Sequel::Model. To support
|
34
|
+
# custom class, instance, and dataset methods, such as those added by
|
35
|
+
# other plugins, you can use the appropriate <tt>sql_comments_*_methods</tt>
|
36
|
+
# class method:
|
37
|
+
#
|
38
|
+
# Album.sql_comments_class_methods :first_by_name # example from finder plugin, with :mod option
|
39
|
+
# Album.sql_comments_instance_methods :lazy_attribute_lookup # lazy_attributes plugin
|
40
|
+
# Album.sql_comments_dataset_methods :to_csv # csv_serializer plugin
|
41
|
+
#
|
42
|
+
# In order for the sql_comments plugin to work, the sql_comments
|
43
|
+
# Database extension must be loaded into the model's database.
|
44
|
+
#
|
45
|
+
# Note that in order to make sure SQL comments are included, some
|
46
|
+
# optimizations are disabled if this plugin is loaded.
|
47
|
+
#
|
48
|
+
# Usage:
|
49
|
+
#
|
50
|
+
# # Make all model subclasses support automatic SQL comments
|
51
|
+
# # (called before loading subclasses)
|
52
|
+
# Sequel::Model.plugin :sql_comments
|
53
|
+
#
|
54
|
+
# # Make the Album class support automatic SQL comments
|
55
|
+
# Album.plugin :sql_comments
|
56
|
+
module SqlComments
|
57
|
+
# Define a method +meth+ on the given module +mod+ that will use automatic
|
58
|
+
# SQL comments with the given model, method_type, and method.
|
59
|
+
def self.def_sql_commend_method(mod, model, method_type, meth)
|
60
|
+
mod.send(:define_method, meth) do |*a, &block|
|
61
|
+
model.db.with_comments(:model=>model, :method_type=>method_type, :method=>meth) do
|
62
|
+
super(*a, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# :nocov:
|
66
|
+
mod.send(:ruby2_keywords, meth) if mod.respond_to?(:ruby2_keywords, true)
|
67
|
+
# :nocov:
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.configure(model)
|
71
|
+
model.send(:reset_fast_pk_lookup_sql)
|
72
|
+
end
|
73
|
+
|
74
|
+
module ClassMethods
|
75
|
+
# Use automatic SQL comments for the given class methods.
|
76
|
+
def sql_comments_class_methods(*meths)
|
77
|
+
_sql_comments_methods(singleton_class, :class, meths)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Use automatic SQL comments for the given instance methods.
|
81
|
+
def sql_comments_instance_methods(*meths)
|
82
|
+
_sql_comments_methods(self, :instance, meths)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Use automatic SQL comments for the given dataset methods.
|
86
|
+
def sql_comments_dataset_methods(*meths)
|
87
|
+
unless @_sql_comments_dataset_module
|
88
|
+
dataset_module(@_sql_comments_dataset_module = Module.new)
|
89
|
+
end
|
90
|
+
_sql_comments_methods(@_sql_comments_dataset_module, :dataset, meths)
|
91
|
+
end
|
92
|
+
|
93
|
+
[:[], :create, :find, :find_or_create, :with_pk, :with_pk!].each do |meth|
|
94
|
+
define_method(meth) do |*a, &block|
|
95
|
+
db.with_comments(:model=>self, :method_type=>:class, :method=>meth) do
|
96
|
+
super(*a, &block)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
# :nocov:
|
100
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
101
|
+
# :nocov:
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Don't optimize the fast PK lookups, as it uses static SQL that
|
107
|
+
# won't support the SQL comments.
|
108
|
+
def reset_fast_pk_lookup_sql
|
109
|
+
@fast_pk_lookup_sql = @fast_instance_delete_sql = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Define automatic SQL comment methods in +mod+ for each method in +meths+,
|
113
|
+
# with the given +method_type+.
|
114
|
+
def _sql_comments_methods(mod, method_type, meths)
|
115
|
+
meths.each do |meth|
|
116
|
+
SqlComments.def_sql_commend_method(mod, self, method_type, meth)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module InstanceMethods
|
122
|
+
[:delete, :destroy, :lock!, :refresh, :save, :save_changes, :update, :update_fields].each do |meth|
|
123
|
+
define_method(meth) do |*a, &block|
|
124
|
+
t = Sequel.current
|
125
|
+
return super(*a, &block) if (hash = Sequel.synchronize{db.comment_hashes[t]}) && hash[:model]
|
126
|
+
|
127
|
+
db.with_comments(:model=>model, :method_type=>:instance, :method=>meth) do
|
128
|
+
super(*a, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# :nocov:
|
132
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
133
|
+
# :nocov:
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Do not use a placeholder loader for associations.
|
139
|
+
def _associated_object_loader(opts, dynamic_opts)
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Use SQL comments on normal association load queries, showing they are association loads.
|
144
|
+
def _load_associated_objects(opts, dynamic_opts=OPTS)
|
145
|
+
db.with_comments(:model=>model, :method_type=>:association_load, :association=>opts[:name]) do
|
146
|
+
super
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
module DatasetMethods
|
152
|
+
Dataset::ACTION_METHODS.each do |meth|
|
153
|
+
define_method(meth) do |*a, &block|
|
154
|
+
t = Sequel.current
|
155
|
+
return super(*a, &block) if (hash = Sequel.synchronize{db.comment_hashes[t]}) && hash[:model]
|
156
|
+
|
157
|
+
db.with_comments(:model=>model, :method_type=>:dataset, :method=>meth) do
|
158
|
+
super(*a, &block)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
# :nocov:
|
162
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
163
|
+
# :nocov:
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
# Add the association name as part of the eager load data, so
|
169
|
+
# perform_eager_load has access to it.
|
170
|
+
def prepare_eager_load(a, reflections, eager_assoc)
|
171
|
+
res = super
|
172
|
+
|
173
|
+
reflections.each do |r|
|
174
|
+
res[r[:eager_loader]][:association] = r[:name]
|
175
|
+
end
|
176
|
+
|
177
|
+
res
|
178
|
+
end
|
179
|
+
|
180
|
+
# Use SQL comments on eager load queries, showing they are eager loads.
|
181
|
+
def perform_eager_load(loader, eo)
|
182
|
+
db.with_comments(:model=>model, :method_type=>:association_eager_load, :method=>nil, :association=>eo[:association]) do
|
183
|
+
super
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -64,6 +64,9 @@ module Sequel
|
|
64
64
|
def self.configure(model, opts=OPTS)
|
65
65
|
model.instance_exec do
|
66
66
|
@static_cache_frozen = opts.fetch(:frozen, true)
|
67
|
+
if @static_cache_frozen && defined?(::Sequel::Plugins::ForbidLazyLoad::ClassMethods) && is_a?(::Sequel::Plugins::ForbidLazyLoad::ClassMethods)
|
68
|
+
extend ForbidLazyLoadClassMethods
|
69
|
+
end
|
67
70
|
load_cache
|
68
71
|
end
|
69
72
|
end
|
@@ -87,7 +90,7 @@ module Sequel
|
|
87
90
|
# array containing the number of instances specified (single integer
|
88
91
|
# argument).
|
89
92
|
def first(*args)
|
90
|
-
if
|
93
|
+
if defined?(yield) || args.length > 1 || (args.length == 1 && !args[0].is_a?(Integer))
|
91
94
|
super
|
92
95
|
else
|
93
96
|
@all.first(*args)
|
@@ -246,6 +249,41 @@ module Sequel
|
|
246
249
|
end
|
247
250
|
end
|
248
251
|
|
252
|
+
module ForbidLazyLoadClassMethods
|
253
|
+
# Do not forbid lazy loading for single object retrieval.
|
254
|
+
def cache_get_pk(pk)
|
255
|
+
primary_key_lookup(pk)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Use static cache to return first arguments.
|
259
|
+
def first(*args)
|
260
|
+
if !defined?(yield) && args.empty?
|
261
|
+
if o = @all.first
|
262
|
+
_static_cache_frozen_copy(o)
|
263
|
+
end
|
264
|
+
else
|
265
|
+
super
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
# Return a frozen copy of the object that does not have lazy loading
|
272
|
+
# forbidden.
|
273
|
+
def _static_cache_frozen_copy(o)
|
274
|
+
o = call(Hash[o.values])
|
275
|
+
o.errors.freeze
|
276
|
+
o.freeze
|
277
|
+
end
|
278
|
+
|
279
|
+
# Do not forbid lazy loading for single object retrieval.
|
280
|
+
def primary_key_lookup(pk)
|
281
|
+
if o = cache[pk]
|
282
|
+
_static_cache_frozen_copy(o)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
249
287
|
module InstanceMethods
|
250
288
|
# Disallowing destroying the object unless the frozen: false option was used.
|
251
289
|
def before_destroy
|
@@ -26,7 +26,11 @@ module Sequel
|
|
26
26
|
module ClassMethods
|
27
27
|
# Dump the in-memory cached rows to the cache file.
|
28
28
|
def dump_static_cache_cache
|
29
|
-
|
29
|
+
static_cache_cache = {}
|
30
|
+
@static_cache_cache.sort.each do |k, v|
|
31
|
+
static_cache_cache[k] = v
|
32
|
+
end
|
33
|
+
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(static_cache_cache))}
|
30
34
|
nil
|
31
35
|
end
|
32
36
|
|
@@ -5,7 +5,7 @@ module Sequel
|
|
5
5
|
# The subclasses plugin keeps track of all subclasses of the
|
6
6
|
# current model class. Direct subclasses are available via the
|
7
7
|
# subclasses method, and all descendent classes are available via the
|
8
|
-
#
|
8
|
+
# descendants method:
|
9
9
|
#
|
10
10
|
# c = Class.new(Sequel::Model)
|
11
11
|
# c.plugin :subclasses
|
@@ -16,7 +16,7 @@ module Sequel
|
|
16
16
|
# sc1.subclasses # [ssc1]
|
17
17
|
# sc2.subclasses # []
|
18
18
|
# ssc1.subclasses # []
|
19
|
-
# c.
|
19
|
+
# c.descendants # [sc1, ssc1, sc2]
|
20
20
|
#
|
21
21
|
# You can also finalize the associations and then freeze the classes
|
22
22
|
# in all descendent classes. Doing so is a recommended practice after
|
@@ -35,9 +35,14 @@ module Sequel
|
|
35
35
|
# class B < Sequel::Model; end
|
36
36
|
# a # => [A, B]
|
37
37
|
module Subclasses
|
38
|
+
NEED_SUBCLASSES = !Object.respond_to?(:subclasses) || Object.method(:subclasses).source_location
|
39
|
+
private_constant :NEED_SUBCLASSES
|
40
|
+
|
38
41
|
# Initialize the subclasses instance variable for the model.
|
39
42
|
def self.apply(model, &block)
|
40
|
-
|
43
|
+
# :nocov:
|
44
|
+
model.instance_variable_set(:@subclasses, []) if NEED_SUBCLASSES
|
45
|
+
# :nocov:
|
41
46
|
model.instance_variable_set(:@on_subclass, block)
|
42
47
|
end
|
43
48
|
|
@@ -46,21 +51,31 @@ module Sequel
|
|
46
51
|
# class created.
|
47
52
|
attr_reader :on_subclass
|
48
53
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
54
|
+
# :nocov:
|
55
|
+
if NEED_SUBCLASSES
|
56
|
+
# All subclasses for the current model. Does not
|
57
|
+
# include the model itself.
|
58
|
+
attr_reader :subclasses
|
59
|
+
end
|
60
|
+
# :nocov:
|
52
61
|
|
53
62
|
# All descendent classes of this model.
|
54
|
-
def
|
55
|
-
Sequel.synchronize{subclasses.dup}.map{|x| [x] + x.send(:
|
63
|
+
def descendants
|
64
|
+
Sequel.synchronize{subclasses.dup}.map{|x| [x] + x.send(:descendants)}.flatten
|
56
65
|
end
|
57
66
|
|
67
|
+
# SEQUEL6: Remove
|
68
|
+
alias descendents descendants
|
69
|
+
|
58
70
|
# Freeze all descendent classes. This also finalizes the associations for those
|
59
71
|
# classes before freezing.
|
60
|
-
def
|
61
|
-
|
72
|
+
def freeze_descendants
|
73
|
+
descendants.each(&:finalize_associations).each(&:freeze)
|
62
74
|
end
|
63
75
|
|
76
|
+
# SEQUEL6: Remove
|
77
|
+
alias freeze_descendents freeze_descendants
|
78
|
+
|
64
79
|
Plugins.inherited_instance_variables(self, :@subclasses=>lambda{|v| []}, :@on_subclass=>nil)
|
65
80
|
|
66
81
|
private
|
@@ -70,7 +85,9 @@ module Sequel
|
|
70
85
|
# in the subclass.
|
71
86
|
def inherited(subclass)
|
72
87
|
super
|
73
|
-
|
88
|
+
# :nocov:
|
89
|
+
Sequel.synchronize{subclasses << subclass} if NEED_SUBCLASSES
|
90
|
+
# :nocov:
|
74
91
|
on_subclass.call(subclass) if on_subclass
|
75
92
|
end
|
76
93
|
end
|