sequel 4.46.0 → 4.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +210 -0
- data/Rakefile +1 -1
- data/doc/advanced_associations.rdoc +1 -1
- data/doc/opening_databases.rdoc +3 -2
- data/doc/release_notes/4.47.0.txt +56 -0
- data/doc/release_notes/4.48.0.txt +293 -0
- data/doc/release_notes/4.49.0.txt +222 -0
- data/lib/sequel/adapters/ado/access.rb +2 -1
- data/lib/sequel/adapters/do/postgres.rb +5 -2
- data/lib/sequel/adapters/ibmdb.rb +30 -8
- data/lib/sequel/adapters/jdbc/as400.rb +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +12 -3
- data/lib/sequel/adapters/jdbc/derby.rb +4 -5
- data/lib/sequel/adapters/jdbc/h2.rb +10 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +16 -2
- data/lib/sequel/adapters/jdbc/postgresql.rb +46 -20
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +9 -7
- data/lib/sequel/adapters/jdbc/sqlserver.rb +20 -6
- data/lib/sequel/adapters/jdbc.rb +39 -23
- data/lib/sequel/adapters/mock.rb +27 -19
- data/lib/sequel/adapters/mysql.rb +17 -16
- data/lib/sequel/adapters/mysql2.rb +5 -6
- data/lib/sequel/adapters/oracle.rb +5 -9
- data/lib/sequel/adapters/postgres.rb +91 -103
- data/lib/sequel/adapters/shared/db2.rb +22 -6
- data/lib/sequel/adapters/shared/mssql.rb +5 -4
- data/lib/sequel/adapters/shared/mysql.rb +79 -25
- data/lib/sequel/adapters/shared/oracle.rb +26 -3
- data/lib/sequel/adapters/shared/postgres.rb +199 -95
- data/lib/sequel/adapters/shared/sqlanywhere.rb +23 -10
- data/lib/sequel/adapters/shared/sqlite.rb +72 -82
- data/lib/sequel/adapters/sqlanywhere.rb +4 -1
- data/lib/sequel/adapters/sqlite.rb +5 -3
- data/lib/sequel/adapters/swift/postgres.rb +5 -2
- data/lib/sequel/adapters/tinytds.rb +0 -5
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/adapters/utils/pg_types.rb +2 -76
- data/lib/sequel/ast_transformer.rb +1 -1
- data/lib/sequel/connection_pool/sharded_single.rb +1 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +1 -1
- data/lib/sequel/connection_pool/single.rb +2 -2
- data/lib/sequel/connection_pool/threaded.rb +2 -2
- data/lib/sequel/connection_pool.rb +9 -2
- data/lib/sequel/core.rb +2 -2
- data/lib/sequel/database/connecting.rb +8 -8
- data/lib/sequel/database/dataset.rb +6 -3
- data/lib/sequel/database/dataset_defaults.rb +14 -1
- data/lib/sequel/database/misc.rb +1 -1
- data/lib/sequel/database/query.rb +3 -0
- data/lib/sequel/database/schema_methods.rb +1 -1
- data/lib/sequel/dataset/actions.rb +72 -10
- data/lib/sequel/dataset/dataset_module.rb +58 -0
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/misc.rb +1 -0
- data/lib/sequel/dataset/prepared_statements.rb +3 -3
- data/lib/sequel/dataset/query.rb +22 -11
- data/lib/sequel/dataset.rb +1 -1
- data/lib/sequel/exceptions.rb +8 -0
- data/lib/sequel/extensions/_model_pg_row.rb +5 -2
- data/lib/sequel/extensions/core_extensions.rb +4 -1
- data/lib/sequel/extensions/current_datetime_timestamp.rb +2 -1
- data/lib/sequel/extensions/date_arithmetic.rb +1 -0
- data/lib/sequel/extensions/duplicate_columns_handler.rb +3 -3
- data/lib/sequel/extensions/empty_array_ignore_nulls.rb +3 -0
- data/lib/sequel/extensions/filter_having.rb +2 -0
- data/lib/sequel/extensions/freeze_datasets.rb +2 -0
- data/lib/sequel/extensions/from_block.rb +1 -1
- data/lib/sequel/extensions/graph_each.rb +2 -2
- data/lib/sequel/extensions/hash_aliases.rb +2 -0
- data/lib/sequel/extensions/identifier_mangling.rb +0 -7
- data/lib/sequel/extensions/meta_def.rb +2 -0
- data/lib/sequel/extensions/migration.rb +11 -8
- data/lib/sequel/extensions/no_auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/null_dataset.rb +1 -0
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +207 -130
- data/lib/sequel/extensions/pg_hstore.rb +38 -20
- data/lib/sequel/extensions/pg_inet.rb +18 -6
- data/lib/sequel/extensions/pg_interval.rb +19 -12
- data/lib/sequel/extensions/pg_json.rb +25 -14
- data/lib/sequel/extensions/pg_json_ops.rb +2 -2
- data/lib/sequel/extensions/pg_range.rb +133 -100
- data/lib/sequel/extensions/pg_range_ops.rb +4 -3
- data/lib/sequel/extensions/pg_row.rb +68 -39
- data/lib/sequel/extensions/pg_row_ops.rb +11 -5
- data/lib/sequel/extensions/query_literals.rb +2 -0
- data/lib/sequel/extensions/ruby18_symbol_extensions.rb +2 -0
- data/lib/sequel/extensions/s.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +29 -25
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +3 -1
- data/lib/sequel/extensions/sequel_4_dataset_methods.rb +83 -0
- data/lib/sequel/extensions/server_block.rb +32 -15
- data/lib/sequel/extensions/set_overrides.rb +2 -2
- data/lib/sequel/extensions/string_agg.rb +0 -1
- data/lib/sequel/extensions/symbol_aref.rb +0 -4
- data/lib/sequel/model/associations.rb +35 -7
- data/lib/sequel/model/base.rb +113 -87
- data/lib/sequel/model/dataset_module.rb +5 -43
- data/lib/sequel/model/errors.rb +2 -1
- data/lib/sequel/model/inflections.rb +17 -5
- data/lib/sequel/model.rb +26 -58
- data/lib/sequel/plugins/active_model.rb +2 -2
- data/lib/sequel/plugins/association_autoreloading.rb +2 -0
- data/lib/sequel/plugins/association_dependencies.rb +3 -3
- data/lib/sequel/plugins/association_pks.rb +73 -46
- data/lib/sequel/plugins/association_proxies.rb +1 -1
- data/lib/sequel/plugins/auto_validations.rb +6 -2
- data/lib/sequel/plugins/boolean_readers.rb +2 -2
- data/lib/sequel/plugins/boolean_subsets.rb +1 -1
- data/lib/sequel/plugins/caching.rb +19 -13
- data/lib/sequel/plugins/class_table_inheritance.rb +24 -13
- data/lib/sequel/plugins/column_conflicts.rb +7 -2
- data/lib/sequel/plugins/column_select.rb +3 -3
- data/lib/sequel/plugins/composition.rb +2 -2
- data/lib/sequel/plugins/csv_serializer.rb +8 -8
- data/lib/sequel/plugins/dataset_associations.rb +25 -13
- data/lib/sequel/plugins/defaults_setter.rb +13 -1
- data/lib/sequel/plugins/eager_each.rb +1 -1
- data/lib/sequel/plugins/force_encoding.rb +2 -2
- data/lib/sequel/plugins/hook_class_methods.rb +9 -12
- data/lib/sequel/plugins/identifier_columns.rb +2 -0
- data/lib/sequel/plugins/instance_filters.rb +3 -1
- data/lib/sequel/plugins/instance_hooks.rb +17 -9
- data/lib/sequel/plugins/json_serializer.rb +19 -12
- data/lib/sequel/plugins/lazy_attributes.rb +8 -7
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +2 -0
- data/lib/sequel/plugins/modification_detection.rb +3 -0
- data/lib/sequel/plugins/nested_attributes.rb +6 -2
- data/lib/sequel/plugins/pg_array_associations.rb +5 -0
- data/lib/sequel/plugins/pg_row.rb +4 -2
- data/lib/sequel/plugins/pg_typecast_on_load.rb +2 -0
- data/lib/sequel/plugins/prepared_statements.rb +1 -0
- data/lib/sequel/plugins/rcte_tree.rb +4 -24
- data/lib/sequel/plugins/serialization.rb +9 -15
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -3
- data/lib/sequel/plugins/split_values.rb +6 -5
- data/lib/sequel/plugins/static_cache.rb +31 -25
- data/lib/sequel/plugins/subset_conditions.rb +3 -1
- data/lib/sequel/plugins/table_select.rb +1 -1
- data/lib/sequel/plugins/touch.rb +4 -2
- data/lib/sequel/plugins/validation_class_methods.rb +5 -6
- data/lib/sequel/plugins/validation_helpers.rb +14 -8
- data/lib/sequel/plugins/xml_serializer.rb +4 -4
- data/lib/sequel/sql.rb +18 -9
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +115 -14
- data/spec/adapters/mssql_spec.rb +4 -4
- data/spec/adapters/mysql_spec.rb +83 -29
- data/spec/adapters/oracle_spec.rb +28 -24
- data/spec/adapters/postgres_spec.rb +40 -24
- data/spec/adapters/sqlanywhere_spec.rb +88 -86
- data/spec/adapters/sqlite_spec.rb +29 -24
- data/spec/bin_spec.rb +7 -1
- data/spec/core/connection_pool_spec.rb +45 -14
- data/spec/core/database_spec.rb +155 -0
- data/spec/core/dataset_spec.rb +219 -36
- data/spec/core/schema_spec.rb +16 -0
- data/spec/core/spec_helper.rb +1 -0
- data/spec/core_extensions_spec.rb +6 -2
- data/spec/extensions/active_model_spec.rb +1 -1
- data/spec/extensions/arbitrary_servers_spec.rb +1 -1
- data/spec/extensions/association_pks_spec.rb +34 -2
- data/spec/extensions/auto_literal_strings_spec.rb +5 -1
- data/spec/extensions/auto_validations_spec.rb +2 -0
- data/spec/extensions/boolean_readers_spec.rb +1 -1
- data/spec/extensions/boolean_subsets_spec.rb +1 -1
- data/spec/extensions/class_table_inheritance_spec.rb +106 -19
- data/spec/extensions/column_conflicts_spec.rb +11 -0
- data/spec/extensions/column_select_spec.rb +1 -0
- data/spec/extensions/composition_spec.rb +13 -0
- data/spec/extensions/connection_validator_spec.rb +1 -1
- data/spec/extensions/dataset_associations_spec.rb +20 -8
- data/spec/extensions/defaults_setter_spec.rb +15 -1
- data/spec/extensions/filter_having_spec.rb +5 -3
- data/spec/extensions/hash_aliases_spec.rb +3 -1
- data/spec/extensions/identifier_columns_spec.rb +3 -1
- data/spec/extensions/implicit_subquery_spec.rb +4 -2
- data/spec/extensions/json_serializer_spec.rb +18 -0
- data/spec/extensions/lazy_attributes_spec.rb +3 -3
- data/spec/extensions/many_through_many_spec.rb +4 -4
- data/spec/extensions/meta_def_spec.rb +9 -0
- data/spec/extensions/migration_spec.rb +3 -3
- data/spec/extensions/nested_attributes_spec.rb +14 -3
- data/spec/extensions/no_auto_literal_strings_spec.rb +8 -4
- data/spec/extensions/null_dataset_spec.rb +1 -1
- data/spec/extensions/pg_array_associations_spec.rb +29 -18
- data/spec/extensions/pg_array_spec.rb +44 -25
- data/spec/extensions/pg_hstore_spec.rb +10 -0
- data/spec/extensions/pg_inet_spec.rb +26 -0
- data/spec/extensions/pg_interval_spec.rb +20 -0
- data/spec/extensions/pg_json_spec.rb +24 -0
- data/spec/extensions/pg_range_spec.rb +98 -14
- data/spec/extensions/pg_row_spec.rb +14 -4
- data/spec/extensions/pg_typecast_on_load_spec.rb +11 -9
- data/spec/extensions/prepared_statements_safe_spec.rb +1 -1
- data/spec/extensions/query_literals_spec.rb +3 -1
- data/spec/extensions/schema_dumper_spec.rb +108 -94
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +10 -6
- data/spec/extensions/sequel_4_dataset_methods_spec.rb +121 -0
- data/spec/extensions/serialization_spec.rb +1 -1
- data/spec/extensions/server_block_spec.rb +7 -0
- data/spec/extensions/single_table_inheritance_spec.rb +17 -1
- data/spec/extensions/spec_helper.rb +7 -1
- data/spec/extensions/static_cache_spec.rb +75 -24
- data/spec/extensions/string_agg_spec.rb +1 -1
- data/spec/extensions/touch_spec.rb +9 -0
- data/spec/extensions/validation_helpers_spec.rb +10 -5
- data/spec/extensions/whitelist_security_spec.rb +26 -0
- data/spec/integration/associations_test.rb +8 -0
- data/spec/integration/dataset_test.rb +45 -44
- data/spec/integration/model_test.rb +53 -4
- data/spec/integration/plugin_test.rb +28 -4
- data/spec/integration/prepared_statement_test.rb +3 -0
- data/spec/integration/schema_test.rb +21 -1
- data/spec/integration/transaction_test.rb +40 -40
- data/spec/model/association_reflection_spec.rb +43 -1
- data/spec/model/associations_spec.rb +29 -9
- data/spec/model/class_dataset_methods_spec.rb +20 -4
- data/spec/model/dataset_methods_spec.rb +12 -3
- data/spec/model/eager_loading_spec.rb +8 -8
- data/spec/model/model_spec.rb +45 -1
- data/spec/model/plugins_spec.rb +34 -0
- data/spec/model/record_spec.rb +1 -1
- data/spec/spec_config.rb +2 -0
- metadata +11 -4
- data/spec/adapters/firebird_spec.rb +0 -405
- data/spec/adapters/informix_spec.rb +0 -100
|
@@ -29,11 +29,16 @@ module Sequel
|
|
|
29
29
|
# # Make the Album class handle column conflicts automatically
|
|
30
30
|
# Album.plugin :column_conflicts
|
|
31
31
|
module ColumnConflicts
|
|
32
|
-
|
|
33
|
-
def self.configure(model)
|
|
32
|
+
def self.apply(model)
|
|
34
33
|
model.instance_eval do
|
|
35
34
|
@get_column_conflicts = {}
|
|
36
35
|
@set_column_conflicts = {}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check for column conflicts on the current model if the model has a dataset.
|
|
40
|
+
def self.configure(model)
|
|
41
|
+
model.instance_eval do
|
|
37
42
|
check_column_conflicts if @dataset
|
|
38
43
|
end
|
|
39
44
|
end
|
|
@@ -41,15 +41,15 @@ module Sequel
|
|
|
41
41
|
# qualifying them with table's name.
|
|
42
42
|
def convert_input_dataset(ds)
|
|
43
43
|
ds = super
|
|
44
|
-
if !ds.opts[:select] && (from = ds.opts[:from]) && from.length == 1 && !ds.opts[:join]
|
|
44
|
+
if !ds.opts[:select] && (from = ds.opts[:from]) && from.length == 1 && !ds.opts[:join] # SEQUE5: just !ds.opts[:select]
|
|
45
45
|
if db.supports_schema_parsing?
|
|
46
|
-
cols = check_non_connection_error{db.schema(ds)}
|
|
46
|
+
cols = check_non_connection_error(false){db.schema(ds)}
|
|
47
47
|
if cols
|
|
48
48
|
cols = cols.map{|c, _| c}
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
if cols ||= check_non_connection_error{ds.columns}
|
|
52
|
+
if cols ||= check_non_connection_error(false){ds.columns}
|
|
53
53
|
ds = ds.select(*cols.map{|c| Sequel.qualify(ds.first_source, Sequel.identifier(c))})
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -123,9 +123,9 @@ module Sequel
|
|
|
123
123
|
setters = setter_meths.zip(cov_methods)
|
|
124
124
|
opts[:decomposer] = proc do
|
|
125
125
|
if (o = compositions[name]).nil?
|
|
126
|
-
setter_meths.each{|sm|
|
|
126
|
+
setter_meths.each{|sm| set_column_value(sm, nil)}
|
|
127
127
|
else
|
|
128
|
-
setters.each{|sm, cm|
|
|
128
|
+
setters.each{|sm, cm| set_column_value(sm, o.send(cm))}
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
end
|
|
@@ -76,9 +76,9 @@ module Sequel
|
|
|
76
76
|
|
|
77
77
|
# Set up the column readers to do deserialization and the column writers
|
|
78
78
|
# to save the value in deserialized_values
|
|
79
|
-
def self.configure(model, opts =
|
|
79
|
+
def self.configure(model, opts = OPTS)
|
|
80
80
|
model.instance_eval do
|
|
81
|
-
@csv_serializer_opts = (@csv_serializer_opts ||
|
|
81
|
+
@csv_serializer_opts = (@csv_serializer_opts || OPTS).merge(opts)
|
|
82
82
|
end
|
|
83
83
|
end
|
|
84
84
|
|
|
@@ -87,7 +87,7 @@ module Sequel
|
|
|
87
87
|
attr_reader :csv_serializer_opts
|
|
88
88
|
|
|
89
89
|
# Attempt to parse an array of instances from the given CSV string
|
|
90
|
-
def array_from_csv(csv, opts =
|
|
90
|
+
def array_from_csv(csv, opts = OPTS)
|
|
91
91
|
CSV.parse(csv, process_csv_serializer_opts(opts)).map do |row|
|
|
92
92
|
row = row.to_hash
|
|
93
93
|
row.delete(nil)
|
|
@@ -105,13 +105,13 @@ module Sequel
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
# Attempt to parse a single instance from the given CSV string
|
|
108
|
-
def from_csv(csv, opts =
|
|
108
|
+
def from_csv(csv, opts = OPTS)
|
|
109
109
|
new.from_csv(csv, opts)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
# Convert the options hash to one that can be passed to CSV.
|
|
113
113
|
def process_csv_serializer_opts(opts)
|
|
114
|
-
opts = (csv_serializer_opts ||
|
|
114
|
+
opts = (csv_serializer_opts || OPTS).merge(opts)
|
|
115
115
|
opts_cols = opts.delete(:columns)
|
|
116
116
|
opts_include = opts.delete(:include)
|
|
117
117
|
opts_except = opts.delete(:except)
|
|
@@ -136,7 +136,7 @@ module Sequel
|
|
|
136
136
|
#
|
|
137
137
|
# :headers :: The headers to use for the CSV line. Use nil for a header
|
|
138
138
|
# to specify the column should be ignored.
|
|
139
|
-
def from_csv(csv, opts =
|
|
139
|
+
def from_csv(csv, opts = OPTS)
|
|
140
140
|
row = CSV.parse_line(csv, model.process_csv_serializer_opts(opts)).to_hash
|
|
141
141
|
row.delete(nil)
|
|
142
142
|
set(row)
|
|
@@ -151,7 +151,7 @@ module Sequel
|
|
|
151
151
|
# output, ignoring all other columns
|
|
152
152
|
# :include :: Symbol or Array of Symbols specifying non-column
|
|
153
153
|
# attributes to include in the CSV output.
|
|
154
|
-
def to_csv(opts =
|
|
154
|
+
def to_csv(opts = OPTS)
|
|
155
155
|
opts = model.process_csv_serializer_opts(opts)
|
|
156
156
|
|
|
157
157
|
CSV.generate(opts) do |csv|
|
|
@@ -168,7 +168,7 @@ module Sequel
|
|
|
168
168
|
#
|
|
169
169
|
# :array :: An array of instances. If this is not provided, calls #all
|
|
170
170
|
# on the receiver to get the array.
|
|
171
|
-
def to_csv(opts =
|
|
171
|
+
def to_csv(opts = OPTS)
|
|
172
172
|
opts = model.process_csv_serializer_opts({:columns=>columns}.merge!(opts))
|
|
173
173
|
items = opts.delete(:array) || self
|
|
174
174
|
|
|
@@ -93,22 +93,34 @@ module Sequel
|
|
|
93
93
|
where(r.qualify(r.join_table_alias, r[:left_keys])=>sds.select(*r.qualify(model.table_name, r[:left_primary_key_columns])))
|
|
94
94
|
ds.where(r.qualified_right_primary_key=>r.send(:apply_filter_by_associations_limit_strategy, mds))
|
|
95
95
|
when :many_through_many, :one_through_many
|
|
96
|
-
|
|
97
|
-
edges << r.final_edge
|
|
98
|
-
if fre = r.reverse_edges.first
|
|
99
|
-
table = fre[:table]
|
|
100
|
-
left = fre[:left]
|
|
101
|
-
mds = model.join(fe[:table], Array(fe[:right]).zip(Array(fe[:left])), :implicit_qualifier=>model.table_name)
|
|
102
|
-
else
|
|
103
|
-
table = fe[:table]
|
|
104
|
-
left = edges.first[:left]
|
|
105
|
-
edges = []
|
|
96
|
+
if r.reverse_edges.empty?
|
|
106
97
|
mds = r.associated_dataset
|
|
98
|
+
fe = r.edges.first
|
|
99
|
+
selection = Array(r.qualify(fe[:table], r.final_edge[:left]))
|
|
100
|
+
predicate_key = r.qualify(fe[:table], fe[:right])
|
|
101
|
+
else
|
|
102
|
+
mds = model.dataset
|
|
103
|
+
iq = model.table_name
|
|
104
|
+
edges = r.edges.map(&:dup)
|
|
105
|
+
edges << r.final_edge.dup
|
|
106
|
+
edges.each do |e|
|
|
107
|
+
alias_expr = e[:table]
|
|
108
|
+
aliaz = mds.unused_table_alias(e[:table])
|
|
109
|
+
unless aliaz == alias_expr
|
|
110
|
+
alias_expr = Sequel.as(e[:table], aliaz)
|
|
111
|
+
end
|
|
112
|
+
e[:alias] = aliaz
|
|
113
|
+
mds = mds.join(alias_expr, Array(e[:right]).zip(Array(e[:left])), :implicit_qualifier=>iq)
|
|
114
|
+
iq = nil
|
|
115
|
+
end
|
|
116
|
+
fe, f1e, f2e = edges.values_at(0, -1, -2)
|
|
117
|
+
selection = Array(r.qualify(f2e[:alias], f1e[:left]))
|
|
118
|
+
predicate_key = r.qualify(fe[:alias], fe[:right])
|
|
107
119
|
end
|
|
120
|
+
|
|
108
121
|
mds = mds.
|
|
109
|
-
select(*
|
|
110
|
-
where(
|
|
111
|
-
edges.each{|e| mds = mds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
|
|
122
|
+
select(*selection).
|
|
123
|
+
where(predicate_key=>sds.select(*r.qualify(model.table_name, r[:left_primary_key_columns])))
|
|
112
124
|
ds.where(r.qualified_right_primary_key=>r.send(:apply_filter_by_associations_limit_strategy, mds))
|
|
113
125
|
when :pg_array_to_many
|
|
114
126
|
ds.where(Sequel[r.primary_key=>sds.select{Sequel.pg_array_op(r.qualify(r[:model].table_name, r[:key])).unnest}])
|
|
@@ -13,6 +13,16 @@ module Sequel
|
|
|
13
13
|
# album = Album.new(:a=>1, :b=>3)
|
|
14
14
|
# album.a # => 1
|
|
15
15
|
# album.b # => 3
|
|
16
|
+
#
|
|
17
|
+
# You can manually set default values as well:
|
|
18
|
+
#
|
|
19
|
+
# Album.default_values[:a] = 4
|
|
20
|
+
# Album.new.a # => 4
|
|
21
|
+
#
|
|
22
|
+
# You can also provide procs to set default values:
|
|
23
|
+
#
|
|
24
|
+
# Album.default_values[:a] = lambda{Date.today}
|
|
25
|
+
# Album.new.a # => Date.today
|
|
16
26
|
#
|
|
17
27
|
# Usage:
|
|
18
28
|
#
|
|
@@ -36,6 +46,8 @@ module Sequel
|
|
|
36
46
|
|
|
37
47
|
Plugins.after_set_dataset(self, :set_default_values)
|
|
38
48
|
|
|
49
|
+
Plugins.inherited_instance_variables(self, :@default_values=>:dup)
|
|
50
|
+
|
|
39
51
|
# Freeze default values when freezing model class
|
|
40
52
|
def freeze
|
|
41
53
|
@default_values.freeze
|
|
@@ -48,7 +60,7 @@ module Sequel
|
|
|
48
60
|
def set_default_values
|
|
49
61
|
h = {}
|
|
50
62
|
@db_schema.each{|k, v| h[k] = convert_default_value(v[:ruby_default]) unless v[:ruby_default].nil?} if @db_schema
|
|
51
|
-
@default_values = h
|
|
63
|
+
@default_values = h.merge!(@default_values || {})
|
|
52
64
|
end
|
|
53
65
|
|
|
54
66
|
# Handle the CURRENT_DATE and CURRENT_TIMESTAMP values specially by returning an appropriate Date or
|
|
@@ -14,11 +14,11 @@ module Sequel
|
|
|
14
14
|
#
|
|
15
15
|
# Usage:
|
|
16
16
|
#
|
|
17
|
-
# # Force all strings to be
|
|
17
|
+
# # Force all strings to be UTF-8 encoded in a all model subclasses
|
|
18
18
|
# # (called before loading subclasses)
|
|
19
19
|
# Sequel::Model.plugin :force_encoding, 'UTF-8'
|
|
20
20
|
#
|
|
21
|
-
# # Force the encoding for the Album model to
|
|
21
|
+
# # Force the encoding for the Album model to UTF-8
|
|
22
22
|
# Album.plugin :force_encoding
|
|
23
23
|
# Album.forced_encoding = 'UTF-8'
|
|
24
24
|
module ForceEncoding
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Sequel
|
|
4
4
|
module Plugins
|
|
5
|
-
# Sequel's built-in
|
|
5
|
+
# Sequel's built-in hook_class_methods plugin is designed for backwards
|
|
6
6
|
# compatibility. Its use is not encouraged, it is recommended to use
|
|
7
|
-
# instance methods and super instead of this plugin.
|
|
8
|
-
#
|
|
7
|
+
# instance methods and super instead of this plugin. This plugin allows
|
|
8
|
+
# calling class methods with blocks to define hooks:
|
|
9
9
|
#
|
|
10
10
|
# # Block only, can cause duplicate hooks if code is reloaded
|
|
11
11
|
# before_save{self.created_at = Time.now}
|
|
@@ -15,17 +15,14 @@ module Sequel
|
|
|
15
15
|
# before_save(:set_created_at)
|
|
16
16
|
#
|
|
17
17
|
# Pretty much anything you can do with a hook class method, you can also
|
|
18
|
-
# do with an instance method instead
|
|
18
|
+
# do with an instance method instead (making sure to call super), which is
|
|
19
|
+
# the recommended way to add hooks in Sequel:
|
|
19
20
|
#
|
|
20
21
|
# def before_save
|
|
21
|
-
#
|
|
22
|
+
# super
|
|
22
23
|
# self.created_at = Time.now
|
|
23
24
|
# end
|
|
24
25
|
#
|
|
25
|
-
# Note that returning false in any before hook block will skip further
|
|
26
|
-
# before hooks and abort the action. So if a before_save hook block returns
|
|
27
|
-
# false, future before_save hook blocks are not called, and the save is aborted.
|
|
28
|
-
#
|
|
29
26
|
# Usage:
|
|
30
27
|
#
|
|
31
28
|
# # Allow use of hook class methods in all model subclasses (called before loading subclasses)
|
|
@@ -91,7 +88,6 @@ module Sequel
|
|
|
91
88
|
# arbitrary code execution.
|
|
92
89
|
def add_hook_type(*hooks)
|
|
93
90
|
Sequel::Deprecation.deprecate("Sequel::Model.add_hook_type", "You should add your own hook types manually")
|
|
94
|
-
Model::HOOKS.concat(hooks)
|
|
95
91
|
hooks.each do |hook|
|
|
96
92
|
@hooks[hook] = []
|
|
97
93
|
instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__)
|
|
@@ -141,7 +137,8 @@ module Sequel
|
|
|
141
137
|
end
|
|
142
138
|
|
|
143
139
|
module InstanceMethods
|
|
144
|
-
|
|
140
|
+
# SEQUEL5: Make :before_save, :before_destroy, :after_save, :after_destroy hooks use metaprogramming instead of specific definitions
|
|
141
|
+
[:before_create, :before_update, :before_validation].each do |h|
|
|
145
142
|
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
146
143
|
def #{h}
|
|
147
144
|
model.hook_blocks(:#{h}) do |b|
|
|
@@ -154,7 +151,7 @@ module Sequel
|
|
|
154
151
|
end
|
|
155
152
|
END
|
|
156
153
|
end
|
|
157
|
-
|
|
154
|
+
[:after_create, :after_update, :after_validation].each{|h| class_eval("def #{h}; super; model.hook_blocks(:#{h}){|b| instance_eval(&b)}; end", __FILE__, __LINE__)}
|
|
158
155
|
|
|
159
156
|
def after_destroy
|
|
160
157
|
super
|
|
@@ -7,7 +7,9 @@ module Sequel
|
|
|
7
7
|
# where you would normally have to drop down to the dataset level
|
|
8
8
|
# to get the necessary control, because you only want to delete or
|
|
9
9
|
# update the rows in certain cases based on the current status of
|
|
10
|
-
# the row in the database.
|
|
10
|
+
# the row in the database. The main purpose of this plugin is to
|
|
11
|
+
# avoid race conditions by relying on the atomic properties of database
|
|
12
|
+
# transactions.
|
|
11
13
|
#
|
|
12
14
|
# class Item < Sequel::Model
|
|
13
15
|
# plugin :instance_filters
|
|
@@ -9,8 +9,7 @@ module Sequel
|
|
|
9
9
|
# All of the standard hooks are supported.
|
|
10
10
|
# Instance level before hooks are executed in reverse order of addition before
|
|
11
11
|
# calling super. Instance level after hooks are executed in order of addition
|
|
12
|
-
# after calling super.
|
|
13
|
-
# false, no more instance level before hooks are called and false is returned.
|
|
12
|
+
# after calling super.
|
|
14
13
|
#
|
|
15
14
|
# Instance level hooks for before and after are cleared after all related
|
|
16
15
|
# after level instance hooks have run. This means that if you add a before_create
|
|
@@ -29,9 +28,12 @@ module Sequel
|
|
|
29
28
|
# # Add the instance hook methods just to Album instances
|
|
30
29
|
# Album.plugin :instance_hooks
|
|
31
30
|
module InstanceHooks
|
|
32
|
-
BEFORE_HOOKS = Sequel::Model::
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
BEFORE_HOOKS, AFTER_HOOKS = Sequel::Model::HOOKS.partition{|l| l.to_s.start_with?('before')}
|
|
32
|
+
Sequel::Deprecation.deprecate_constant(self, :BEFORE_HOOKS)
|
|
33
|
+
Sequel::Deprecation.deprecate_constant(self, :AFTER_HOOKS)
|
|
34
|
+
HOOKS = Sequel::Model::HOOKS
|
|
35
|
+
Sequel::Deprecation.deprecate_constant(self, :HOOKS)
|
|
36
|
+
|
|
35
37
|
# SEQUEL5: Remove
|
|
36
38
|
DEPRECATION_REPLACEMENTS = {
|
|
37
39
|
:after_commit=>"Use obj.after_save_hook{obj.db.after_commit{}} instead",
|
|
@@ -41,7 +43,7 @@ module Sequel
|
|
|
41
43
|
}.freeze
|
|
42
44
|
|
|
43
45
|
module InstanceMethods
|
|
44
|
-
HOOKS.each{|h| class_eval(<<-END , __FILE__, __LINE__+1)}
|
|
46
|
+
Sequel::Model::HOOKS.each{|h| class_eval(<<-END , __FILE__, __LINE__+1)}
|
|
45
47
|
def #{h}_hook(&block)
|
|
46
48
|
#{"Sequel::Deprecation.deprecate('Sequel::Model##{h}_hook in the instance_hooks plugin', #{DEPRECATION_REPLACEMENTS[h].inspect})" if DEPRECATION_REPLACEMENTS[h]}
|
|
47
49
|
raise Sequel::Error, "can't add hooks to frozen object" if frozen?
|
|
@@ -50,8 +52,8 @@ module Sequel
|
|
|
50
52
|
end
|
|
51
53
|
END
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
[:before_create, :before_update, :before_validation].each{|h| class_eval("def #{h}; (@instance_hooks && run_before_instance_hooks(:#{h}) == false) ? false : super end", __FILE__, __LINE__)}
|
|
56
|
+
[:after_create, :after_update].each{|h| class_eval(<<-END, __FILE__, __LINE__ + 1)}
|
|
55
57
|
def #{h}
|
|
56
58
|
super
|
|
57
59
|
return unless @instance_hooks
|
|
@@ -65,6 +67,7 @@ module Sequel
|
|
|
65
67
|
def after_destroy
|
|
66
68
|
super
|
|
67
69
|
return unless @instance_hooks
|
|
70
|
+
# SEQUEL5: Remove commit/rollback
|
|
68
71
|
if ad = @instance_hooks[:after_destroy_commit]
|
|
69
72
|
db.after_commit{ad.each(&:call)}
|
|
70
73
|
end
|
|
@@ -86,6 +89,7 @@ module Sequel
|
|
|
86
89
|
def after_save
|
|
87
90
|
super
|
|
88
91
|
return unless @instance_hooks
|
|
92
|
+
# SEQUEL5: Remove commit/rollback
|
|
89
93
|
if (ac = @instance_hooks[:after_commit])
|
|
90
94
|
db.after_commit{ac.each(&:call)}
|
|
91
95
|
end
|
|
@@ -101,18 +105,22 @@ module Sequel
|
|
|
101
105
|
# Run before_destroy instance hooks.
|
|
102
106
|
def before_destroy
|
|
103
107
|
return super unless @instance_hooks
|
|
108
|
+
# SEQUEL5: Remove commit/rollback
|
|
104
109
|
if adr = @instance_hooks[:after_destroy_rollback]
|
|
105
110
|
db.after_rollback{adr.each(&:call)}
|
|
106
111
|
end
|
|
112
|
+
# SEQUEL5: No false checking
|
|
107
113
|
run_before_instance_hooks(:before_destroy) == false ? false : super
|
|
108
114
|
end
|
|
109
115
|
|
|
110
116
|
# Run before_save instance hooks.
|
|
111
117
|
def before_save
|
|
112
118
|
return super unless @instance_hooks
|
|
119
|
+
# SEQUEL5: Remove commit/rollback
|
|
113
120
|
if ar = @instance_hooks[:after_rollback]
|
|
114
121
|
db.after_rollback{ar.each(&:call)}
|
|
115
122
|
end
|
|
123
|
+
# SEQUEL5: No false checking
|
|
116
124
|
run_before_instance_hooks(:before_save) == false ? false : super
|
|
117
125
|
end
|
|
118
126
|
|
|
@@ -122,7 +130,7 @@ module Sequel
|
|
|
122
130
|
# the beginning of the instance hook's array. For after hooks, add it
|
|
123
131
|
# to the end.
|
|
124
132
|
def add_instance_hook(hook, &block)
|
|
125
|
-
instance_hooks(hook).send(
|
|
133
|
+
instance_hooks(hook).send(hook.to_s.start_with?('before') ? :unshift : :push, block)
|
|
126
134
|
end
|
|
127
135
|
|
|
128
136
|
# An array of instance level hook blocks for the given hook type.
|
|
@@ -62,6 +62,11 @@ module Sequel
|
|
|
62
62
|
#
|
|
63
63
|
# Album.to_json(:array=>[Album[1], Album[2]])
|
|
64
64
|
#
|
|
65
|
+
# All to_json methods take blocks, and if a block is given, it will yield
|
|
66
|
+
# the array or hash before serialization, and will serialize the value
|
|
67
|
+
# the block returns. This allows you to customize the resulting JSON format
|
|
68
|
+
# on a per-call basis.
|
|
69
|
+
#
|
|
65
70
|
# In addition to creating JSON, this plugin also enables Sequel::Model
|
|
66
71
|
# classes to create instances directly from JSON using the from_json class
|
|
67
72
|
# method:
|
|
@@ -126,9 +131,9 @@ module Sequel
|
|
|
126
131
|
module JsonSerializer
|
|
127
132
|
# Set up the column readers to do deserialization and the column writers
|
|
128
133
|
# to save the value in deserialized_values.
|
|
129
|
-
def self.configure(model, opts=
|
|
134
|
+
def self.configure(model, opts=OPTS)
|
|
130
135
|
model.instance_eval do
|
|
131
|
-
@json_serializer_opts = (@json_serializer_opts ||
|
|
136
|
+
@json_serializer_opts = (@json_serializer_opts || OPTS).merge(opts)
|
|
132
137
|
end
|
|
133
138
|
end
|
|
134
139
|
|
|
@@ -222,10 +227,10 @@ module Sequel
|
|
|
222
227
|
if assocs = opts[:associations]
|
|
223
228
|
assocs = case assocs
|
|
224
229
|
when Symbol
|
|
225
|
-
{assocs=>
|
|
230
|
+
{assocs=>OPTS}
|
|
226
231
|
when Array
|
|
227
232
|
assocs_tmp = {}
|
|
228
|
-
assocs.each{|v| assocs_tmp[v] =
|
|
233
|
+
assocs.each{|v| assocs_tmp[v] = OPTS}
|
|
229
234
|
assocs_tmp
|
|
230
235
|
when Hash
|
|
231
236
|
assocs
|
|
@@ -316,7 +321,7 @@ module Sequel
|
|
|
316
321
|
if inc.is_a?(Hash)
|
|
317
322
|
inc.each do |k, v|
|
|
318
323
|
if k.is_a?(Sequel::SQL::AliasedExpression)
|
|
319
|
-
key_name = k.
|
|
324
|
+
key_name = k.alias.to_s
|
|
320
325
|
k = k.expression
|
|
321
326
|
else
|
|
322
327
|
key_name = k.to_s
|
|
@@ -341,7 +346,7 @@ module Sequel
|
|
|
341
346
|
else
|
|
342
347
|
Array(inc).each do |c|
|
|
343
348
|
if c.is_a?(Sequel::SQL::AliasedExpression)
|
|
344
|
-
key_name = c.
|
|
349
|
+
key_name = c.alias.to_s
|
|
345
350
|
c = c.expression
|
|
346
351
|
else
|
|
347
352
|
key_name = c.to_s
|
|
@@ -358,6 +363,7 @@ module Sequel
|
|
|
358
363
|
h = {root => h}
|
|
359
364
|
end
|
|
360
365
|
|
|
366
|
+
h = yield h if block_given?
|
|
361
367
|
Sequel.object_to_json(h, *a)
|
|
362
368
|
end
|
|
363
369
|
end
|
|
@@ -370,6 +376,8 @@ module Sequel
|
|
|
370
376
|
#
|
|
371
377
|
# :array :: An array of instances. If this is not provided,
|
|
372
378
|
# calls #all on the receiver to get the array.
|
|
379
|
+
# :instance_block :: A block to pass to #to_json for each
|
|
380
|
+
# value in the dataset (or :array option).
|
|
373
381
|
# :root :: If set to :collection, wraps the collection
|
|
374
382
|
# in a root object using the pluralized, underscored model
|
|
375
383
|
# name as the key. If set to :instance, only wraps
|
|
@@ -405,16 +413,15 @@ module Sequel
|
|
|
405
413
|
else
|
|
406
414
|
all
|
|
407
415
|
end
|
|
408
|
-
array.map{|obj| Literal.new(Sequel.object_to_json(obj, opts))}
|
|
416
|
+
array.map{|obj| Literal.new(Sequel.object_to_json(obj, opts, &opts[:instance_block]))}
|
|
409
417
|
else
|
|
410
418
|
all
|
|
411
419
|
end
|
|
412
420
|
|
|
413
|
-
if collection_root
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
end
|
|
421
|
+
res = {collection_root => res} if collection_root
|
|
422
|
+
res = yield res if block_given?
|
|
423
|
+
|
|
424
|
+
Sequel.object_to_json(res, *a)
|
|
418
425
|
end
|
|
419
426
|
end
|
|
420
427
|
end
|
|
@@ -92,9 +92,8 @@ module Sequel
|
|
|
92
92
|
# the attribute for just the current object. Return the value of
|
|
93
93
|
# the attribute for the current object.
|
|
94
94
|
def lazy_attribute_lookup(a, opts=OPTS)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
end
|
|
95
|
+
table = opts[:table] || model.table_name
|
|
96
|
+
selection = Sequel.qualify(table, a)
|
|
98
97
|
|
|
99
98
|
if base_ds = opts[:dataset]
|
|
100
99
|
ds = base_ds.where(qualified_pk_hash(table))
|
|
@@ -103,10 +102,8 @@ module Sequel
|
|
|
103
102
|
ds = this
|
|
104
103
|
end
|
|
105
104
|
|
|
106
|
-
selection = Sequel.qualify(table, a)
|
|
107
|
-
|
|
108
105
|
if frozen?
|
|
109
|
-
return ds.
|
|
106
|
+
return ds.get(selection)
|
|
110
107
|
end
|
|
111
108
|
|
|
112
109
|
if retrieved_with
|
|
@@ -115,7 +112,11 @@ module Sequel
|
|
|
115
112
|
id_map = {}
|
|
116
113
|
retrieved_with.each{|o| id_map[o.pk] = o unless o.values.has_key?(a) || o.frozen?}
|
|
117
114
|
predicate_key = composite_pk ? primary_key.map{|k| Sequel.qualify(table, k)} : Sequel.qualify(table, primary_key)
|
|
118
|
-
base_ds.
|
|
115
|
+
base_ds.
|
|
116
|
+
select(*(Array(primary_key).map{|k| Sequel.qualify(table, k)} + [selection])).
|
|
117
|
+
where(predicate_key=>id_map.keys).
|
|
118
|
+
naked.
|
|
119
|
+
each do |row|
|
|
119
120
|
obj = id_map[composite_pk ? row.values_at(*primary_key) : row[primary_key]]
|
|
120
121
|
if obj && !obj.values.has_key?(a)
|
|
121
122
|
obj.values[a] = row[a]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
+
Sequel::Deprecation.deprecate("The many_to_one_pk_lookup plugin", "This plugin was integrated into the default model behavior in Sequel 4.0, and no longer has an effect")
|
|
4
|
+
|
|
3
5
|
module Sequel
|
|
4
6
|
module Plugins
|
|
5
7
|
# Empty plugin module for backwards compatibility
|
|
@@ -17,6 +17,9 @@ module Sequel
|
|
|
17
17
|
# Note that for this plugin to work correctly, the column values must
|
|
18
18
|
# correctly implement the #hash method, returning the same value if
|
|
19
19
|
# the object is equal, and a different value if the object is not equal.
|
|
20
|
+
# As this solely uses hash values to check for modification, there may
|
|
21
|
+
# be cases where a modification is made and the hash value is the same,
|
|
22
|
+
# resulting in a false negative.
|
|
20
23
|
#
|
|
21
24
|
# Note that this plugin causes a performance hit for all retrieved
|
|
22
25
|
# objects, so it shouldn't be used in cases where performance is a
|
|
@@ -70,7 +70,7 @@ module Sequel
|
|
|
70
70
|
#
|
|
71
71
|
# Then you can do:
|
|
72
72
|
#
|
|
73
|
-
# artist.
|
|
73
|
+
# artist.update_fields(params[:artist], [:name, :albums_artists])
|
|
74
74
|
#
|
|
75
75
|
# To save changes to the artist, create the first album and associate it to the artist,
|
|
76
76
|
# and update the other existing associated album.
|
|
@@ -127,7 +127,11 @@ module Sequel
|
|
|
127
127
|
reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
|
|
128
128
|
reflections.each do |r|
|
|
129
129
|
r[:nested_attributes] = opts
|
|
130
|
-
r[:nested_attributes][:unmatched_pk] ||=
|
|
130
|
+
r[:nested_attributes][:unmatched_pk] ||= (
|
|
131
|
+
if opts.has_key?(:strict)
|
|
132
|
+
Sequel::Deprecation.deprecate("The nested_attributes :strict option", "Use the :unmatched_pk option instead")
|
|
133
|
+
end
|
|
134
|
+
opts.delete(:strict) == false ? :ignore : :raise)
|
|
131
135
|
r[:nested_attributes][:reject_if] ||= block
|
|
132
136
|
def_nested_attribute_method(r)
|
|
133
137
|
end
|
|
@@ -297,6 +297,11 @@ module Sequel
|
|
|
297
297
|
end
|
|
298
298
|
end
|
|
299
299
|
|
|
300
|
+
# Add the pg_array extension to the database
|
|
301
|
+
def self.apply(model)
|
|
302
|
+
model.db.extension(:pg_array)
|
|
303
|
+
end
|
|
304
|
+
|
|
300
305
|
module ClassMethods
|
|
301
306
|
# Create a many_to_pg_array association, for the case where the associated
|
|
302
307
|
# table contains the array with foreign keys pointing to the current table.
|
|
@@ -53,7 +53,9 @@ module Sequel
|
|
|
53
53
|
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345'))
|
|
54
54
|
module PgRow
|
|
55
55
|
ROW = 'ROW'.freeze
|
|
56
|
+
Sequel::Deprecation.deprecate_constant(self, :ROW)
|
|
56
57
|
CAST = '::'.freeze
|
|
58
|
+
Sequel::Deprecation.deprecate_constant(self, :CAST)
|
|
57
59
|
|
|
58
60
|
# When loading the extension, make sure the database has the pg_row extension
|
|
59
61
|
# loaded, load the custom database extensions, and automatically register the
|
|
@@ -75,9 +77,9 @@ module Sequel
|
|
|
75
77
|
module InstanceMethods
|
|
76
78
|
# Literalize the model instance and append it to the sql.
|
|
77
79
|
def sql_literal_append(ds, sql)
|
|
78
|
-
sql << ROW
|
|
80
|
+
sql << 'ROW'
|
|
79
81
|
ds.literal_append(sql, values.values_at(*columns))
|
|
80
|
-
sql <<
|
|
82
|
+
sql << '::'
|
|
81
83
|
ds.quote_schema_table_append(sql, model.dataset.first_source_table)
|
|
82
84
|
end
|
|
83
85
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
+
Sequel::Deprecation.deprecate("The pg_typecast_on_load plugin", "This plugin is only useful on the deprecated do and switch adapters")
|
|
4
|
+
|
|
3
5
|
module Sequel
|
|
4
6
|
module Plugins
|
|
5
7
|
# The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
|