sequel 5.58.0 → 5.60.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/README.rdoc +1 -1
- data/bin/sequel +11 -3
- data/doc/opening_databases.rdoc +4 -5
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/lib/sequel/adapters/postgres.rb +52 -11
- data/lib/sequel/adapters/shared/postgres.rb +36 -15
- data/lib/sequel/adapters/shared/sqlite.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +1 -0
- data/lib/sequel/database/schema_methods.rb +3 -0
- data/lib/sequel/extensions/date_arithmetic.rb +35 -7
- data/lib/sequel/extensions/is_distinct_from.rb +3 -1
- data/lib/sequel/extensions/pg_json_ops.rb +52 -0
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +12 -0
- data/lib/sequel/model/base.rb +14 -4
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/sql_comments.rb +4 -4
- data/lib/sequel/plugins/tactical_eager_loading.rb +7 -0
- data/lib/sequel/version.rb +2 -2
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd8695696d6b55edf6337870f3ff172aeb6fe2ef9ba4bbc23daae0af26908649
|
4
|
+
data.tar.gz: 2483bbb3549adbcb9f179165119051b7bb234d6e714e4afa116c0ffddf379dc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0527cba1fcc06d8001cbd55473b3af5c23a9f5c004113fc24aa6f9e029c5ed2cf4f938bf184d79922164cdc3ea502d12860d46d64237bdbd306bf5ce50f59ffa
|
7
|
+
data.tar.gz: e13c17553592773e70c0e9a6753438444a5245f3cd0664c6c5a6c0a08a271d1d92180b110c7b7e5981f7ca6dc2b18d90da3feeb05a14ae863fcfd99d5673650e
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
=== 5.60.1 (2022-09-02)
|
2
|
+
|
3
|
+
* Revert conversion of respond_to? to defined?, as it breaks with unused refinements on Ruby 2 (jeremyevans) (#1919)
|
4
|
+
|
5
|
+
=== 5.60.0 (2022-09-01)
|
6
|
+
|
7
|
+
* Support arbitrary expressions for date_arithmetic interval values on PostgreSQL 9.4+ (jeremyevans)
|
8
|
+
|
9
|
+
* Support native IS DISTINCT FROM on SQLite 3.39+ instead of emulating support in the is_distinct_from extension (jeremyevans)
|
10
|
+
|
11
|
+
* Support HAVING without GROUP BY on SQLite 3.39+ (jeremyevans)
|
12
|
+
|
13
|
+
* Convert most respond_to? calls to equivalent defined? for better performance (jeremyevans)
|
14
|
+
|
15
|
+
=== 5.59.0 (2022-08-01)
|
16
|
+
|
17
|
+
* Set :allow_eager association option to false for instance specific associations without eager loaders (jeremyevans)
|
18
|
+
|
19
|
+
* Add require_valid_schema plugin for checking that model classes have schema parsed as expected (jeremyevans)
|
20
|
+
|
21
|
+
* Model classes created from aliased expressions and literal strings no longer use the simple table optimization (jeremyevans)
|
22
|
+
|
23
|
+
* Model code that does not swallow connection errors will now also not swallow disconnect errors (jeremyevans) (#1892)
|
24
|
+
|
25
|
+
* Add is_json and is_not_json methods to the pg_json_ops extension, for the PostgreSQL 15+ IS [NOT] JSON operator (jeremyevans)
|
26
|
+
|
27
|
+
* Support :security_invoker view option on PostgreSQL 15+, for views where access uses permissions of user instead of owner (jeremyevans)
|
28
|
+
|
29
|
+
* Support :nulls_distinct index option on PostgreSQL 15+, for NULLS [NOT] DISTINCT (jeremyevans)
|
30
|
+
|
31
|
+
* Support sequel-postgres-pr driver in the postgres adapter (jeremyevans)
|
32
|
+
|
1
33
|
=== 5.58.0 (2022-07-01)
|
2
34
|
|
3
35
|
* Support :disable_split_materialized Database option on MySQL to work around optimizer bug in MariaDB 10.5+ affecting association tests (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -416,7 +416,7 @@ As with +delete+, +update+ affects all rows in the dataset, so +where+ first,
|
|
416
416
|
|
417
417
|
=== Merging records
|
418
418
|
|
419
|
-
Merging records using the SQL MERGE
|
419
|
+
Merging records using the SQL MERGE statement is done using <tt>merge*</tt> methods.
|
420
420
|
You use +merge_using+ to specify the merge source and join conditions.
|
421
421
|
You can use +merge_insert+, +merge_delete+, and/or +merge_update+ to set the
|
422
422
|
INSERT, DELETE, and UPDATE clauses for the merge. +merge_insert+ takes the same
|
data/bin/sequel
CHANGED
@@ -194,7 +194,11 @@ begin
|
|
194
194
|
TO_DB = connect_proc[db2]
|
195
195
|
same_db = DB.database_type==TO_DB.database_type
|
196
196
|
index_opts = {:same_db=>same_db}
|
197
|
+
|
198
|
+
# :nocov:
|
197
199
|
index_opts[:index_names] = :namespace if !DB.global_index_namespace? && TO_DB.global_index_namespace?
|
200
|
+
# :nocov:
|
201
|
+
|
198
202
|
if DB.database_type == :sqlite && !same_db
|
199
203
|
# SQLite integer types allows 64-bit integers
|
200
204
|
TO_DB.extension :integer64
|
@@ -212,18 +216,20 @@ begin
|
|
212
216
|
puts "Begin copying data"
|
213
217
|
DB.transaction do
|
214
218
|
TO_DB.transaction do
|
219
|
+
all_status_lines = ENV['SEQUEL_BIN_STATUS_ALL_LINES']
|
220
|
+
|
215
221
|
DB.tables.each do |table|
|
216
222
|
puts "Begin copying records for table: #{table}"
|
217
223
|
time = Time.now
|
218
224
|
to_ds = TO_DB.from(table)
|
219
225
|
j = 0
|
220
226
|
DB.from(table).each do |record|
|
221
|
-
|
227
|
+
to_ds.insert(record)
|
228
|
+
j += 1
|
229
|
+
if Time.now - time > 5 || all_status_lines
|
222
230
|
puts "Status: #{j} records copied"
|
223
231
|
time = Time.now
|
224
232
|
end
|
225
|
-
to_ds.insert(record)
|
226
|
-
j += 1
|
227
233
|
end
|
228
234
|
puts "Finished copying #{j} records for table: #{table}"
|
229
235
|
end
|
@@ -260,8 +266,10 @@ if !ARGV.empty?
|
|
260
266
|
ARGV.each{|v| load(v)}
|
261
267
|
elsif !$stdin.isatty
|
262
268
|
eval($stdin.read)
|
269
|
+
# :nocov:
|
263
270
|
else
|
264
271
|
require 'irb'
|
265
272
|
puts "Your database is stored in DB..."
|
266
273
|
IRB.start
|
267
274
|
end
|
275
|
+
# :nocov:
|
data/doc/opening_databases.rdoc
CHANGED
@@ -312,15 +312,14 @@ The following additional options are supported:
|
|
312
312
|
|
313
313
|
=== postgres
|
314
314
|
|
315
|
-
Requires: pg (or postgres-pr/postgres-compat if pg is not available)
|
315
|
+
Requires: pg (or sequel/postgres-pr or postgres-pr/postgres-compat if pg is not available)
|
316
316
|
|
317
|
-
The Sequel postgres adapter works with the pg and postgres-pr ruby libraries.
|
317
|
+
The Sequel postgres adapter works with the pg, sequel-postgres-pr, jeremyevans-postgres-pr, and postgres-pr ruby libraries.
|
318
318
|
The pg library is the best supported, as it supports real bound variables and prepared statements.
|
319
319
|
If the pg library is being used, Sequel will also attempt to load the sequel_pg library, which is
|
320
320
|
a C extension that optimizes performance when Sequel is used with pg. All users of Sequel who
|
321
|
-
use pg are encouraged to install sequel_pg. For users who want to use postgres-pr
|
322
|
-
with C extensions, it is recommended to use
|
323
|
-
the upstream postgres-pr gem, and is regularly tested with Sequel.
|
321
|
+
use pg are encouraged to install sequel_pg. For users who want to use one of the postgres-pr
|
322
|
+
libraries to avoid issues with C extensions, it is recommended to use sequel-postgres-pr.
|
324
323
|
|
325
324
|
The following additional options are supported:
|
326
325
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A require_valid_schema plugin has been added, for checking that
|
4
|
+
model classes have schema parsed as expected. By default, model
|
5
|
+
classes are not required to have valid schema, because it is
|
6
|
+
allowed to have model classes based on arbitrary datasets (such
|
7
|
+
as those using joins or set-returning functions), and it is not
|
8
|
+
possible to determine the schema for arbitary datasets.
|
9
|
+
|
10
|
+
Sequel swallows non-connection errors when trying to parse schema
|
11
|
+
for a model's dataset, but if schema parsing fails when you would
|
12
|
+
expect it to succeed, it results in a model where typecasting does
|
13
|
+
not work as expected.
|
14
|
+
|
15
|
+
The require_valid_schema plugin will raise an error when setting
|
16
|
+
the dataset for a model if schema parsing fails and the dataset
|
17
|
+
uses a simple table where you would expect schema parsing to
|
18
|
+
succeed. You can also provide an argument of :warn when loading
|
19
|
+
the plugin, to warn instead of raising an error.
|
20
|
+
|
21
|
+
This plugin may not work correctly in all cases for all adapters,
|
22
|
+
especially external adapters. Adapters are not required to support
|
23
|
+
schema parsing. Even if supported, adapters may not support
|
24
|
+
parsing schema for qualified tables, or parsing schema for views.
|
25
|
+
You should consider this plugin as a possible safety net. Users
|
26
|
+
are encouraged to try using it and report any unexpected breakage,
|
27
|
+
as that may help improve schema parsing in adapters that Sequel
|
28
|
+
ships.
|
29
|
+
|
30
|
+
* is_json and is_not_json methods have been added to the pg_json_ops
|
31
|
+
extension, for the IS [NOT] JSON operator supported in PostgreSQL
|
32
|
+
15+.
|
33
|
+
|
34
|
+
* Index creation methods on PostgreSQL 15+ now support a
|
35
|
+
:nulls_distinct option, for NULLS [NOT] DISTINCT. This allows you
|
36
|
+
to create unique indexes where NULL values are not considered
|
37
|
+
distinct.
|
38
|
+
|
39
|
+
* View creation methods on PostgreSQL 15+ now support a
|
40
|
+
:security_invoker option to create a view where access is
|
41
|
+
determined by the permissions of the role that is accessing the
|
42
|
+
view, instead of the role that created the view.
|
43
|
+
|
44
|
+
= Other Improvements
|
45
|
+
|
46
|
+
* The :allow_eager association option is now set to false by default
|
47
|
+
for associations explicitly marked as :instance_specific, if the
|
48
|
+
:eager_loader association is not given.
|
49
|
+
|
50
|
+
* The postgres adapter now supports the sequel-postgres-pr driver.
|
51
|
+
The sequel-postgres-pr driver is a slimmed down fork of the
|
52
|
+
postgres-pr driver designed specifically for use by Sequel.
|
53
|
+
|
54
|
+
* Model code that explicitly does not swallow connection errors
|
55
|
+
will also now not swallow disconnect errors. This can fix issues
|
56
|
+
where model classes are being loaded at runtime, and the query to
|
57
|
+
get the columns/schema for the model uses a connection that has
|
58
|
+
been disconnected.
|
59
|
+
|
60
|
+
* Model classes created from aliased expressions and literal
|
61
|
+
strings no longer use the simple_table optimization, as there
|
62
|
+
are cases where doing so is not safe.
|
63
|
+
|
64
|
+
= Backwards Compatibility
|
65
|
+
|
66
|
+
* The change to not swallow disconnect errors when not swallowing
|
67
|
+
connection errors can result in exceptions being raised which
|
68
|
+
weren't raised previously. In most cases, this will alert you
|
69
|
+
to issues in your application that should be fixed, but it
|
70
|
+
potentially it can result in regressions if you were OK with
|
71
|
+
the errors being swallowed. If this does result in regressions
|
72
|
+
in your application, please file an issue and we can probably
|
73
|
+
add a setting controlling this feature.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* The date_arithmetic extension now supports arbitrary expressions
|
4
|
+
as interval values on PostgreSQL 9.4+. Previously, only integers
|
5
|
+
were supported for the interval values.
|
6
|
+
|
7
|
+
= Other Improvements
|
8
|
+
|
9
|
+
* Most Kernel#respond_to? calls have been converted to equivalent
|
10
|
+
defined? calls for better performance. defined? is a keyword
|
11
|
+
and is about 50% faster for the same behavior.
|
12
|
+
|
13
|
+
* The is_distinct_from extension now supports the IS DISTINCT FROM
|
14
|
+
syntax natively on SQLite 3.39+, instead of emulating it.
|
15
|
+
|
16
|
+
* HAVING without GROUP BY is now supported on SQLite 3.39+.
|
17
|
+
|
18
|
+
* Coverage testing has been significantly expanded. Previously,
|
19
|
+
the core, model, plugin, and extension code had 100% line/branch
|
20
|
+
coverage. 100% line/branch coverage has been added for the
|
21
|
+
core extensions, bin/sequel, and the postgres adapter with the
|
22
|
+
pg driver.
|
@@ -5,6 +5,7 @@ require_relative 'shared/postgres'
|
|
5
5
|
begin
|
6
6
|
require 'pg'
|
7
7
|
|
8
|
+
# :nocov:
|
8
9
|
Sequel::Postgres::PGError = PG::Error if defined?(PG::Error)
|
9
10
|
Sequel::Postgres::PGconn = PG::Connection if defined?(PG::Connection)
|
10
11
|
Sequel::Postgres::PGresult = PG::Result if defined?(PG::Result)
|
@@ -14,30 +15,40 @@ begin
|
|
14
15
|
raise LoadError unless defined?(PGconn::CONNECTION_OK)
|
15
16
|
end
|
16
17
|
|
17
|
-
Sequel::Postgres::USES_PG = true
|
18
18
|
if defined?(PG::TypeMapByClass)
|
19
|
+
# :nocov:
|
19
20
|
type_map = Sequel::Postgres::PG_QUERY_TYPE_MAP = PG::TypeMapByClass.new
|
20
21
|
type_map[Integer] = PG::TextEncoder::Integer.new
|
21
22
|
type_map[FalseClass] = type_map[TrueClass] = PG::TextEncoder::Boolean.new
|
22
23
|
type_map[Float] = PG::TextEncoder::Float.new
|
23
24
|
end
|
25
|
+
|
26
|
+
Sequel::Postgres::USES_PG = true
|
24
27
|
rescue LoadError => e
|
28
|
+
# :nocov:
|
25
29
|
begin
|
26
|
-
require 'postgres-pr
|
27
|
-
Sequel::Postgres::USES_PG = false
|
30
|
+
require 'sequel/postgres-pr'
|
28
31
|
rescue LoadError
|
29
|
-
|
32
|
+
begin
|
33
|
+
require 'postgres-pr/postgres-compat'
|
34
|
+
rescue LoadError
|
35
|
+
raise e
|
36
|
+
end
|
30
37
|
end
|
38
|
+
Sequel::Postgres::USES_PG = false
|
39
|
+
# :nocov:
|
31
40
|
end
|
32
41
|
|
33
42
|
module Sequel
|
34
43
|
module Postgres
|
35
|
-
|
44
|
+
# :nocov:
|
45
|
+
if USES_PG
|
36
46
|
# Whether the given sequel_pg version integer is supported.
|
37
47
|
def self.sequel_pg_version_supported?(version)
|
38
48
|
version >= 10617
|
39
49
|
end
|
40
50
|
end
|
51
|
+
# :nocov:
|
41
52
|
|
42
53
|
# PGconn subclass for connection specific methods used with the
|
43
54
|
# pg or postgres-pr driver.
|
@@ -45,7 +56,9 @@ module Sequel
|
|
45
56
|
# The underlying exception classes to reraise as disconnect errors
|
46
57
|
# instead of regular database errors.
|
47
58
|
DISCONNECT_ERROR_CLASSES = [IOError, Errno::EPIPE, Errno::ECONNRESET]
|
59
|
+
# :nocov:
|
48
60
|
if defined?(::PG::ConnectionBad)
|
61
|
+
# :nocov:
|
49
62
|
DISCONNECT_ERROR_CLASSES << ::PG::ConnectionBad
|
50
63
|
end
|
51
64
|
DISCONNECT_ERROR_CLASSES.freeze
|
@@ -71,11 +84,14 @@ module Sequel
|
|
71
84
|
# are SQL strings.
|
72
85
|
attr_reader :prepared_statements
|
73
86
|
|
87
|
+
# :nocov:
|
74
88
|
unless public_method_defined?(:async_exec_params)
|
75
89
|
alias async_exec_params async_exec
|
76
90
|
end
|
77
|
-
|
78
|
-
#
|
91
|
+
elsif !const_defined?(:CONNECTION_OK)
|
92
|
+
# Handle old postgres-pr
|
93
|
+
# sequel-postgres-pr already implements this API
|
94
|
+
|
79
95
|
CONNECTION_OK = -1
|
80
96
|
|
81
97
|
# Escape bytea values. Uses historical format instead of hex
|
@@ -111,6 +127,7 @@ module Sequel
|
|
111
127
|
alias cmd_tuples cmdtuples
|
112
128
|
end
|
113
129
|
end
|
130
|
+
# :nocov:
|
114
131
|
|
115
132
|
# Raise a Sequel::DatabaseDisconnectError if a one of the disconnect
|
116
133
|
# error classes is raised, or a PGError is raised and the connection
|
@@ -204,7 +221,9 @@ module Sequel
|
|
204
221
|
:sslmode => opts[:sslmode],
|
205
222
|
:sslrootcert => opts[:sslrootcert]
|
206
223
|
}.delete_if { |key, value| blank_object?(value) }
|
224
|
+
# :nocov:
|
207
225
|
connection_params.merge!(opts[:driver_options]) if opts[:driver_options]
|
226
|
+
# :nocov:
|
208
227
|
conn = Adapter.connect(opts[:conn_str] || connection_params)
|
209
228
|
|
210
229
|
conn.instance_variable_set(:@prepared_statements, {})
|
@@ -212,6 +231,13 @@ module Sequel
|
|
212
231
|
if receiver = opts[:notice_receiver]
|
213
232
|
conn.set_notice_receiver(&receiver)
|
214
233
|
end
|
234
|
+
|
235
|
+
# :nocov:
|
236
|
+
if conn.respond_to?(:type_map_for_queries=) && defined?(PG_QUERY_TYPE_MAP)
|
237
|
+
# :nocov:
|
238
|
+
conn.type_map_for_queries = PG_QUERY_TYPE_MAP
|
239
|
+
end
|
240
|
+
# :nocov:
|
215
241
|
else
|
216
242
|
unless typecast_value_boolean(@opts.fetch(:force_standard_strings, true))
|
217
243
|
raise Error, "Cannot create connection using postgres-pr unless force_standard_strings is set"
|
@@ -226,12 +252,11 @@ module Sequel
|
|
226
252
|
opts[:password]
|
227
253
|
)
|
228
254
|
end
|
255
|
+
# :nocov:
|
229
256
|
|
230
257
|
conn.instance_variable_set(:@db, self)
|
231
|
-
if USES_PG && conn.respond_to?(:type_map_for_queries=) && defined?(PG_QUERY_TYPE_MAP)
|
232
|
-
conn.type_map_for_queries = PG_QUERY_TYPE_MAP
|
233
|
-
end
|
234
258
|
|
259
|
+
# :nocov:
|
235
260
|
if encoding = opts[:encoding] || opts[:charset]
|
236
261
|
if conn.respond_to?(:set_client_encoding)
|
237
262
|
conn.set_client_encoding(encoding)
|
@@ -239,6 +264,7 @@ module Sequel
|
|
239
264
|
conn.async_exec("set client_encoding to '#{encoding}'")
|
240
265
|
end
|
241
266
|
end
|
267
|
+
# :nocov:
|
242
268
|
|
243
269
|
connection_configuration_sqls(opts).each{|sql| conn.execute(sql)}
|
244
270
|
conn
|
@@ -265,7 +291,9 @@ module Sequel
|
|
265
291
|
nil
|
266
292
|
end
|
267
293
|
|
294
|
+
# :nocov:
|
268
295
|
if USES_PG && Object.const_defined?(:PG) && ::PG.const_defined?(:Constants) && ::PG::Constants.const_defined?(:PG_DIAG_SCHEMA_NAME)
|
296
|
+
# :nocov:
|
269
297
|
# Return a hash of information about the related PGError (or Sequel::DatabaseError that
|
270
298
|
# wraps a PGError), with the following entries (any of which may be +nil+):
|
271
299
|
#
|
@@ -316,7 +344,9 @@ module Sequel
|
|
316
344
|
synchronize(opts[:server]){|conn| check_database_errors{_execute(conn, sql, opts, &block)}}
|
317
345
|
end
|
318
346
|
|
347
|
+
# :nocov:
|
319
348
|
if USES_PG
|
349
|
+
# :nocov:
|
320
350
|
# +copy_table+ uses PostgreSQL's +COPY TO STDOUT+ SQL statement to return formatted
|
321
351
|
# results directly to the caller. This method is only supported if pg is the
|
322
352
|
# underlying ruby driver. This method should only be called if you want
|
@@ -509,8 +539,10 @@ module Sequel
|
|
509
539
|
def adapter_initialize
|
510
540
|
@use_iso_date_format = typecast_value_boolean(@opts.fetch(:use_iso_date_format, true))
|
511
541
|
initialize_postgres_adapter
|
542
|
+
# :nocov:
|
512
543
|
add_conversion_proc(17, method(:unescape_bytea)) if USES_PG
|
513
544
|
add_conversion_proc(1082, TYPE_TRANSLATOR_DATE) if @use_iso_date_format
|
545
|
+
# :nocov:
|
514
546
|
self.convert_infinite_timestamps = @opts[:convert_infinite_timestamps]
|
515
547
|
end
|
516
548
|
|
@@ -520,19 +552,22 @@ module Sequel
|
|
520
552
|
rescue => e
|
521
553
|
raise_error(e, :classes=>database_error_classes)
|
522
554
|
end
|
523
|
-
|
524
555
|
# Set the DateStyle to ISO if configured, for faster date parsing.
|
525
556
|
def connection_configuration_sqls(opts=@opts)
|
526
557
|
sqls = super
|
558
|
+
# :nocov:
|
527
559
|
sqls << "SET DateStyle = 'ISO'" if @use_iso_date_format
|
560
|
+
# :nocov:
|
528
561
|
sqls
|
529
562
|
end
|
530
563
|
|
564
|
+
# :nocov:
|
531
565
|
if USES_PG
|
532
566
|
def unescape_bytea(s)
|
533
567
|
::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))
|
534
568
|
end
|
535
569
|
end
|
570
|
+
# :nocov:
|
536
571
|
|
537
572
|
DATABASE_ERROR_CLASSES = [PGError].freeze
|
538
573
|
def database_error_classes
|
@@ -546,7 +581,9 @@ module Sequel
|
|
546
581
|
end
|
547
582
|
|
548
583
|
def database_exception_sqlstate(exception, opts)
|
584
|
+
# :nocov:
|
549
585
|
if exception.respond_to?(:result) && (result = exception.result)
|
586
|
+
# :nocov:
|
550
587
|
result.error_field(PGresult::PG_DIAG_SQLSTATE)
|
551
588
|
end
|
552
589
|
end
|
@@ -656,7 +693,9 @@ module Sequel
|
|
656
693
|
clone(:where=>Sequel.lit(['CURRENT OF '], Sequel.identifier(cursor_name)))
|
657
694
|
end
|
658
695
|
|
696
|
+
# :nocov:
|
659
697
|
if USES_PG
|
698
|
+
# :nocov:
|
660
699
|
PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
|
661
700
|
|
662
701
|
# PostgreSQL specific argument mapper used for mapping the named
|
@@ -803,6 +842,7 @@ module Sequel
|
|
803
842
|
end
|
804
843
|
end
|
805
844
|
|
845
|
+
# :nocov:
|
806
846
|
if Sequel::Postgres::USES_PG && !ENV['NO_SEQUEL_PG']
|
807
847
|
begin
|
808
848
|
require 'sequel_pg'
|
@@ -814,3 +854,4 @@ if Sequel::Postgres::USES_PG && !ENV['NO_SEQUEL_PG']
|
|
814
854
|
rescue LoadError
|
815
855
|
end
|
816
856
|
end
|
857
|
+
# :nocov:
|
@@ -230,7 +230,6 @@ module Sequel
|
|
230
230
|
module DatabaseMethods
|
231
231
|
include UnmodifiedIdentifiers::DatabaseMethods
|
232
232
|
|
233
|
-
PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
|
234
233
|
FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'=>:no_action, 'r'=>:restrict, 'c'=>:cascade, 'n'=>:set_null, 'd'=>:set_default}.freeze
|
235
234
|
ON_COMMIT = {:drop => 'DROP', :delete_rows => 'DELETE ROWS', :preserve_rows => 'PRESERVE ROWS'}.freeze
|
236
235
|
ON_COMMIT.each_value(&:freeze)
|
@@ -365,9 +364,10 @@ module Sequel
|
|
365
364
|
|
366
365
|
table_oid = regclass_oid(table)
|
367
366
|
im = input_identifier_meth
|
368
|
-
unless column =
|
367
|
+
unless column = (opts[:column] || ((sch = schema(table).find{|_, sc| sc[:primary_key] && sc[:auto_increment]}) && sch[0]))
|
369
368
|
raise Error, "could not determine column to convert from serial to identity automatically"
|
370
369
|
end
|
370
|
+
column = im.call(column)
|
371
371
|
|
372
372
|
column_num = ds.from(:pg_attribute).
|
373
373
|
where(:attrelid=>table_oid, :attname=>column).
|
@@ -569,10 +569,12 @@ module Sequel
|
|
569
569
|
if server_version >= 90500
|
570
570
|
cpos = Sequel.expr{array_position(co[:conkey], ctable[:attnum])}
|
571
571
|
rpos = Sequel.expr{array_position(co[:confkey], rtable[:attnum])}
|
572
|
+
# :nocov:
|
572
573
|
else
|
573
574
|
range = 0...32
|
574
575
|
cpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:conkey], [x]), x]}, 32, ctable[:attnum])}
|
575
576
|
rpos = Sequel.expr{SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(co[:confkey], [x]), x]}, 32, rtable[:attnum])}
|
577
|
+
# :nocov:
|
576
578
|
end
|
577
579
|
|
578
580
|
ds = metadata_dataset.
|
@@ -653,9 +655,11 @@ module Sequel
|
|
653
655
|
|
654
656
|
if server_version >= 90500
|
655
657
|
order = [Sequel[:indc][:relname], Sequel.function(:array_position, Sequel[:ind][:indkey], Sequel[:att][:attnum])]
|
658
|
+
# :nocov:
|
656
659
|
else
|
657
660
|
range = 0...32
|
658
661
|
order = [Sequel[:indc][:relname], SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(Sequel[:ind][:indkey], [x]), x]}, 32, Sequel[:att][:attnum])]
|
662
|
+
# :nocov:
|
659
663
|
end
|
660
664
|
|
661
665
|
attnums = SQL::Function.new(:ANY, Sequel[:ind][:indkey])
|
@@ -676,8 +680,10 @@ module Sequel
|
|
676
680
|
select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
|
677
681
|
|
678
682
|
ds = ds.where(:indpred=>nil) unless opts[:include_partial]
|
683
|
+
# :nocov:
|
679
684
|
ds = ds.where(:indisready=>true) if server_version >= 80300
|
680
685
|
ds = ds.where(:indislive=>true) if server_version >= 90300
|
686
|
+
# :nocov:
|
681
687
|
|
682
688
|
indexes = {}
|
683
689
|
ds.each do |r|
|
@@ -758,10 +764,12 @@ module Sequel
|
|
758
764
|
seq_ds = metadata_dataset.from(:pg_sequence).where(:seqrelid=>regclass_oid(LiteralString.new(seq)))
|
759
765
|
increment_by = :seqincrement
|
760
766
|
min_value = :seqmin
|
767
|
+
# :nocov:
|
761
768
|
else
|
762
769
|
seq_ds = metadata_dataset.from(LiteralString.new(seq))
|
763
770
|
increment_by = :increment_by
|
764
771
|
min_value = :min_value
|
772
|
+
# :nocov:
|
765
773
|
end
|
766
774
|
|
767
775
|
get{setval(seq, db[table].select(coalesce(max(pk)+seq_ds.select(increment_by), seq_ds.select(min_value))), false)}
|
@@ -774,7 +782,9 @@ module Sequel
|
|
774
782
|
# PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
|
775
783
|
# managing incrementing primary keys.
|
776
784
|
def serial_primary_key_options
|
785
|
+
# :nocov:
|
777
786
|
auto_increment_key = server_version >= 100002 ? :identity : :serial
|
787
|
+
# :nocov:
|
778
788
|
{:primary_key => true, auto_increment_key => true, :type=>Integer}
|
779
789
|
end
|
780
790
|
|
@@ -1152,7 +1162,7 @@ module Sequel
|
|
1152
1162
|
when :hash
|
1153
1163
|
mod, remainder = generator.hash_values
|
1154
1164
|
sql << " FOR VALUES WITH (MODULUS #{literal(mod)}, REMAINDER #{literal(remainder)})"
|
1155
|
-
when :default
|
1165
|
+
else # when :default
|
1156
1166
|
sql << " DEFAULT"
|
1157
1167
|
end
|
1158
1168
|
|
@@ -1247,6 +1257,10 @@ module Sequel
|
|
1247
1257
|
def create_view_prefix_sql(name, options)
|
1248
1258
|
sql = create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
|
1249
1259
|
|
1260
|
+
if options[:security_invoker]
|
1261
|
+
sql += " WITH (security_invoker)"
|
1262
|
+
end
|
1263
|
+
|
1250
1264
|
if tablespace = options[:tablespace]
|
1251
1265
|
sql += " TABLESPACE #{quote_identifier(tablespace)}"
|
1252
1266
|
end
|
@@ -1304,16 +1318,20 @@ module Sequel
|
|
1304
1318
|
def index_definition_sql(table_name, index)
|
1305
1319
|
cols = index[:columns]
|
1306
1320
|
index_name = index[:name] || default_index_name(table_name, cols)
|
1321
|
+
|
1307
1322
|
expr = if o = index[:opclass]
|
1308
1323
|
"(#{Array(cols).map{|c| "#{literal(c)} #{o}"}.join(', ')})"
|
1309
1324
|
else
|
1310
1325
|
literal(Array(cols))
|
1311
1326
|
end
|
1327
|
+
|
1312
1328
|
if_not_exists = " IF NOT EXISTS" if index[:if_not_exists]
|
1313
1329
|
unique = "UNIQUE " if index[:unique]
|
1314
1330
|
index_type = index[:type]
|
1315
1331
|
filter = index[:where] || index[:filter]
|
1316
1332
|
filter = " WHERE #{filter_expr(filter)}" if filter
|
1333
|
+
nulls_distinct = " NULLS#{' NOT' if index[:nulls_distinct] == false} DISTINCT" unless index[:nulls_distinct].nil?
|
1334
|
+
|
1317
1335
|
case index_type
|
1318
1336
|
when :full_text
|
1319
1337
|
expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
|
@@ -1321,7 +1339,8 @@ module Sequel
|
|
1321
1339
|
when :spatial
|
1322
1340
|
index_type = :gist
|
1323
1341
|
end
|
1324
|
-
|
1342
|
+
|
1343
|
+
"CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{nulls_distinct}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
|
1325
1344
|
end
|
1326
1345
|
|
1327
1346
|
# Setup datastructures shared by all postgres adapters.
|
@@ -1347,11 +1366,6 @@ module Sequel
|
|
1347
1366
|
end
|
1348
1367
|
end
|
1349
1368
|
|
1350
|
-
# Use a dollar sign instead of question mark for the argument placeholder.
|
1351
|
-
def prepared_arg_placeholder
|
1352
|
-
PREPARED_ARG_PLACEHOLDER
|
1353
|
-
end
|
1354
|
-
|
1355
1369
|
# Return an expression the oid for the table expr. Used by the metadata parsing
|
1356
1370
|
# code to disambiguate unqualified tables.
|
1357
1371
|
def regclass_oid(expr, opts=OPTS)
|
@@ -1425,10 +1439,14 @@ module Sequel
|
|
1425
1439
|
where{{pg_class[:oid]=>oid}}.
|
1426
1440
|
order{pg_attribute[:attnum]}
|
1427
1441
|
|
1442
|
+
# :nocov:
|
1428
1443
|
if server_version > 100000
|
1444
|
+
# :nocov:
|
1429
1445
|
ds = ds.select_append{pg_attribute[:attidentity]}
|
1430
1446
|
|
1447
|
+
# :nocov:
|
1431
1448
|
if server_version > 120000
|
1449
|
+
# :nocov:
|
1432
1450
|
ds = ds.select_append{Sequel.~(pg_attribute[:attgenerated]=>'').as(:generated)}
|
1433
1451
|
end
|
1434
1452
|
end
|
@@ -1516,7 +1534,9 @@ module Sequel
|
|
1516
1534
|
|
1517
1535
|
# PostgreSQL 9.4+ supports views with check option.
|
1518
1536
|
def view_with_check_option_support
|
1537
|
+
# :nocov:
|
1519
1538
|
:local if server_version >= 90400
|
1539
|
+
# :nocov:
|
1520
1540
|
end
|
1521
1541
|
end
|
1522
1542
|
|
@@ -1912,6 +1932,8 @@ module Sequel
|
|
1912
1932
|
server_version >= 90000
|
1913
1933
|
when :groups, :exclude
|
1914
1934
|
server_version >= 110000
|
1935
|
+
else
|
1936
|
+
false
|
1915
1937
|
end
|
1916
1938
|
end
|
1917
1939
|
|
@@ -2058,12 +2080,11 @@ module Sequel
|
|
2058
2080
|
|
2059
2081
|
# Return the primary key to use for RETURNING in an INSERT statement
|
2060
2082
|
def insert_pk
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
end
|
2083
|
+
(f = opts[:from]) && !f.empty? && (t = f.first)
|
2084
|
+
case t
|
2085
|
+
when Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier
|
2086
|
+
if pk = db.primary_key(t)
|
2087
|
+
Sequel::SQL::Identifier.new(pk)
|
2067
2088
|
end
|
2068
2089
|
end
|
2069
2090
|
end
|
@@ -663,7 +663,7 @@ module Sequel
|
|
663
663
|
|
664
664
|
# HAVING requires GROUP BY on SQLite
|
665
665
|
def having(*cond)
|
666
|
-
raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset")
|
666
|
+
raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") if !@opts[:group] && db.sqlite_version < 33900
|
667
667
|
super
|
668
668
|
end
|
669
669
|
|
@@ -262,6 +262,7 @@ module Sequel
|
|
262
262
|
# operations on the table while the index is being
|
263
263
|
# built.
|
264
264
|
# :if_not_exists :: Only create the index if an index of the same name doesn't already exist.
|
265
|
+
# :nulls_distinct :: Set whether separate NULLs should be considered distinct values in unique indexes.
|
265
266
|
# :opclass :: Set an opclass to use for all columns (per-column opclasses require
|
266
267
|
# custom SQL).
|
267
268
|
# :tablespace :: Specify tablespace for index.
|
@@ -295,6 +295,9 @@ module Sequel
|
|
295
295
|
# in a subquery, if you are providing a Dataset as the source
|
296
296
|
# argument, if should probably call the union method with the
|
297
297
|
# all: true and from_self: false options.
|
298
|
+
# :security_invoker :: Set the security_invoker property on the view, making
|
299
|
+
# the access to the view use the current user's permissions,
|
300
|
+
# instead of the view owner's permissions.
|
298
301
|
# :tablespace :: The tablespace to use for materialized views.
|
299
302
|
def create_view(name, source, options = OPTS)
|
300
303
|
execute_ddl(create_view_sql(name, source, options))
|
@@ -32,6 +32,10 @@
|
|
32
32
|
#
|
33
33
|
# DB[:table].select(add.as(:d)).where(sub > Sequel::CURRENT_TIMESTAMP)
|
34
34
|
#
|
35
|
+
# On most databases, the values you provide for years/months/days/etc. must
|
36
|
+
# be numeric values and not arbitrary SQL expressions. However, on PostgreSQL
|
37
|
+
# 9.4+, use of arbitrary SQL expressions is supported.
|
38
|
+
#
|
35
39
|
# Related module: Sequel::SQL::DateAdd
|
36
40
|
|
37
41
|
#
|
@@ -54,7 +58,16 @@ module Sequel
|
|
54
58
|
interval = interval.parts
|
55
59
|
end
|
56
60
|
parts = {}
|
57
|
-
interval.each
|
61
|
+
interval.each do |k,v|
|
62
|
+
case v
|
63
|
+
when nil
|
64
|
+
# ignore
|
65
|
+
when Numeric
|
66
|
+
parts[k] = -v
|
67
|
+
else
|
68
|
+
parts[k] = Sequel::SQL::NumericExpression.new(:*, v, -1)
|
69
|
+
end
|
70
|
+
end
|
58
71
|
DateAdd.new(expr, parts, opts)
|
59
72
|
end
|
60
73
|
end
|
@@ -68,6 +81,7 @@ module Sequel
|
|
68
81
|
module DatasetMethods
|
69
82
|
DURATION_UNITS = [:years, :months, :days, :hours, :minutes, :seconds].freeze
|
70
83
|
DEF_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s.freeze}).freeze
|
84
|
+
POSTGRES_DURATION_UNITS = DURATION_UNITS.zip([:years, :months, :days, :hours, :mins, :secs].map{|s| s.to_s.freeze}).freeze
|
71
85
|
MYSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s.upcase[0...-1]).freeze}).freeze
|
72
86
|
MSSQL_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| Sequel.lit(s.to_s[0...-1]).freeze}).freeze
|
73
87
|
H2_DURATION_UNITS = DURATION_UNITS.zip(DURATION_UNITS.map{|s| s.to_s[0...-1].freeze}).freeze
|
@@ -87,14 +101,28 @@ module Sequel
|
|
87
101
|
|
88
102
|
cast = case db_type = db.database_type
|
89
103
|
when :postgres
|
90
|
-
|
91
|
-
|
92
|
-
|
104
|
+
casted = Sequel.cast(expr, cast_type)
|
105
|
+
|
106
|
+
if db.server_version >= 90400
|
107
|
+
placeholder = []
|
108
|
+
vals = []
|
109
|
+
each_valid_interval_unit(h, POSTGRES_DURATION_UNITS) do |value, sql_unit|
|
110
|
+
placeholder << "#{', ' unless placeholder.empty?}#{sql_unit} := "
|
111
|
+
vals << value
|
112
|
+
end
|
113
|
+
interval = Sequel.function(:make_interval, Sequel.lit(placeholder, *vals)) unless vals.empty?
|
114
|
+
else
|
115
|
+
parts = String.new
|
116
|
+
each_valid_interval_unit(h, DEF_DURATION_UNITS) do |value, sql_unit|
|
117
|
+
parts << "#{value} #{sql_unit} "
|
118
|
+
end
|
119
|
+
interval = Sequel.cast(parts, :interval) unless parts.empty?
|
93
120
|
end
|
94
|
-
|
95
|
-
|
121
|
+
|
122
|
+
if interval
|
123
|
+
return complex_expression_sql_append(sql, :+, [casted, interval])
|
96
124
|
else
|
97
|
-
return
|
125
|
+
return literal_append(sql, casted)
|
98
126
|
end
|
99
127
|
when :sqlite
|
100
128
|
args = [expr]
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# The is_distinct_from extension adds the ability to use the
|
4
4
|
# SQL standard IS DISTINCT FROM operator, which is similar to the
|
5
5
|
# not equals operator, except that NULL values are considered
|
6
|
-
# equal.
|
6
|
+
# equal. PostgreSQL, SQLite 3.39+, and H2 currently support this operator. On
|
7
7
|
# other databases, support is emulated.
|
8
8
|
#
|
9
9
|
# First, you need to load the extension into the database:
|
@@ -90,6 +90,8 @@ module Sequel
|
|
90
90
|
case db.database_type
|
91
91
|
when :postgres, :h2
|
92
92
|
true
|
93
|
+
when :sqlite
|
94
|
+
db.sqlite_version >= 33900
|
93
95
|
else
|
94
96
|
false
|
95
97
|
end
|
@@ -123,6 +123,15 @@
|
|
123
123
|
# c = Sequel.pg_jsonb_op(:c)
|
124
124
|
# DB[:t].update(c['key1'] => 1.to_json, c['key2'] => "a".to_json)
|
125
125
|
#
|
126
|
+
# On PostgreSQL 15+, the <tt>IS [NOT] JSON</tt> operator is supported:
|
127
|
+
#
|
128
|
+
# j.is_json # j IS JSON
|
129
|
+
# j.is_json(type: :object) # j IS JSON OBJECT
|
130
|
+
# j.is_json(type: :object, unique: true) # j IS JSON OBJECT WITH UNIQUE
|
131
|
+
# j.is_not_json # j IS NOT JSON
|
132
|
+
# j.is_json(type: :array) # j IS NOT JSON ARRAY
|
133
|
+
# j.is_json(unique: true) # j IS NOT JSON WITH UNIQUE
|
134
|
+
#
|
126
135
|
# If you are also using the pg_json extension, you should load it before
|
127
136
|
# loading this extension. Doing so will allow you to use the #op method on
|
128
137
|
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
@@ -151,6 +160,18 @@ module Sequel
|
|
151
160
|
GET_PATH = ["(".freeze, " #> ".freeze, ")".freeze].freeze
|
152
161
|
GET_PATH_TEXT = ["(".freeze, " #>> ".freeze, ")".freeze].freeze
|
153
162
|
|
163
|
+
IS_JSON = ["(".freeze, " IS JSON".freeze, "".freeze, ")".freeze].freeze
|
164
|
+
IS_NOT_JSON = ["(".freeze, " IS NOT JSON".freeze, "".freeze, ")".freeze].freeze
|
165
|
+
EMPTY_STRING = Sequel::LiteralString.new('').freeze
|
166
|
+
WITH_UNIQUE = Sequel::LiteralString.new(' WITH UNIQUE').freeze
|
167
|
+
IS_JSON_MAP = {
|
168
|
+
nil => EMPTY_STRING,
|
169
|
+
:value => Sequel::LiteralString.new(' VALUE').freeze,
|
170
|
+
:scalar => Sequel::LiteralString.new(' SCALAR').freeze,
|
171
|
+
:object => Sequel::LiteralString.new(' OBJECT').freeze,
|
172
|
+
:array => Sequel::LiteralString.new(' ARRAY').freeze
|
173
|
+
}.freeze
|
174
|
+
|
154
175
|
# Get JSON array element or object field as json. If an array is given,
|
155
176
|
# gets the object at the specified path.
|
156
177
|
#
|
@@ -233,6 +254,30 @@ module Sequel
|
|
233
254
|
end
|
234
255
|
end
|
235
256
|
|
257
|
+
# Return whether the json object can be parsed as JSON.
|
258
|
+
#
|
259
|
+
# Options:
|
260
|
+
# :type :: Check whether the json object can be parsed as a specific type
|
261
|
+
# of JSON (:value, :scalar, :object, :array).
|
262
|
+
# :unique :: Check JSON objects for unique keys.
|
263
|
+
#
|
264
|
+
# json_op.is_json # json IS JSON
|
265
|
+
# json_op.is_json(type: :object) # json IS JSON OBJECT
|
266
|
+
# json_op.is_json(unique: true) # json IS JSON WITH UNIQUE
|
267
|
+
def is_json(opts=OPTS)
|
268
|
+
_is_json(IS_JSON, opts)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Return whether the json object cannot be parsed as JSON. The opposite
|
272
|
+
# of #is_json. See #is_json for options.
|
273
|
+
#
|
274
|
+
# json_op.is_not_json # json IS NOT JSON
|
275
|
+
# json_op.is_not_json(type: :object) # json IS NOT JSON OBJECT
|
276
|
+
# json_op.is_not_json(unique: true) # json IS NOT JSON WITH UNIQUE
|
277
|
+
def is_not_json(opts=OPTS)
|
278
|
+
_is_json(IS_NOT_JSON, opts)
|
279
|
+
end
|
280
|
+
|
236
281
|
# Returns a set of keys AS text in the json object.
|
237
282
|
#
|
238
283
|
# json_op.keys # json_object_keys(json)
|
@@ -286,6 +331,13 @@ module Sequel
|
|
286
331
|
|
287
332
|
private
|
288
333
|
|
334
|
+
# Internals of IS [NOT] JSON support
|
335
|
+
def _is_json(lit_array, opts)
|
336
|
+
raise Error, "invalid is_json :type option: #{opts[:type].inspect}" unless type = IS_JSON_MAP[opts[:type]]
|
337
|
+
unique = opts[:unique] ? WITH_UNIQUE : EMPTY_STRING
|
338
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(lit_array, [self, type, unique]))
|
339
|
+
end
|
340
|
+
|
289
341
|
# Return a placeholder literal with the given str and args, wrapped
|
290
342
|
# in an JSONOp or JSONBOp, used by operators that return json or jsonb.
|
291
343
|
def json_op(str, args)
|
@@ -1717,6 +1717,8 @@ module Sequel
|
|
1717
1717
|
# :graph_select :: A column or array of columns to select from the associated table
|
1718
1718
|
# when eagerly loading the association via +eager_graph+. Defaults to all
|
1719
1719
|
# columns in the associated table.
|
1720
|
+
# :instance_specific :: Marks the association as instance specific. Should be used if the association block
|
1721
|
+
# uses instance specific state, or transient state (accessing current date/time, etc.).
|
1720
1722
|
# :limit :: Limit the number of records to the provided value. Use
|
1721
1723
|
# an array with two elements for the value to specify a
|
1722
1724
|
# limit (first element) and an offset (second element).
|
@@ -1856,6 +1858,16 @@ module Sequel
|
|
1856
1858
|
# in certain places to disable optimizations.
|
1857
1859
|
opts[:instance_specific] = _association_instance_specific_default(name)
|
1858
1860
|
end
|
1861
|
+
if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
|
1862
|
+
# For associations explicitly marked as instance specific, or that use the
|
1863
|
+
# :dataset option, where :allow_eager is not set, and no :eager_loader is
|
1864
|
+
# provided, disallow eager loading. In these cases, eager loading is
|
1865
|
+
# unlikely to work. This is not done for implicit setting of :instance_specific,
|
1866
|
+
# because implicit use is done by default for all associations with blocks,
|
1867
|
+
# and the vast majority of associations with blocks use the block for filtering
|
1868
|
+
# in a manner compatible with eager loading.
|
1869
|
+
opts[:allow_eager] = false
|
1870
|
+
end
|
1859
1871
|
opts = assoc_class.new.merge!(opts)
|
1860
1872
|
|
1861
1873
|
if opts[:clone] && !opts.cloneable?(cloned_assoc)
|
data/lib/sequel/model/base.rb
CHANGED
@@ -680,10 +680,11 @@ module Sequel
|
|
680
680
|
|
681
681
|
private
|
682
682
|
|
683
|
-
# Yield to the passed block and if do_raise is false, swallow
|
683
|
+
# Yield to the passed block and if do_raise is false, swallow Sequel::Errors other than DatabaseConnectionError
|
684
|
+
# and DatabaseDisconnectError.
|
684
685
|
def check_non_connection_error(do_raise=require_valid_table)
|
685
686
|
db.transaction(:savepoint=>:only){yield}
|
686
|
-
rescue Sequel::DatabaseConnectionError
|
687
|
+
rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
|
687
688
|
raise
|
688
689
|
rescue Sequel::Error
|
689
690
|
raise if do_raise
|
@@ -693,9 +694,12 @@ module Sequel
|
|
693
694
|
# this model's dataset.
|
694
695
|
def convert_input_dataset(ds)
|
695
696
|
case ds
|
696
|
-
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
697
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
697
698
|
self.simple_table = db.literal(ds).freeze
|
698
699
|
ds = db.from(ds)
|
700
|
+
when SQL::AliasedExpression, LiteralString
|
701
|
+
self.simple_table = nil
|
702
|
+
ds = db.from(ds)
|
699
703
|
when Dataset
|
700
704
|
ds = ds.from_self(:alias=>ds.first_source) if ds.joined_dataset?
|
701
705
|
|
@@ -784,7 +788,7 @@ module Sequel
|
|
784
788
|
schema_hash = {}
|
785
789
|
ds_opts = dataset.opts
|
786
790
|
get_columns = proc{check_non_connection_error{columns} || []}
|
787
|
-
schema_array =
|
791
|
+
schema_array = get_db_schema_array(reload) if db.supports_schema_parsing?
|
788
792
|
if schema_array
|
789
793
|
schema_array.each{|k,v| schema_hash[k] = v}
|
790
794
|
|
@@ -821,6 +825,12 @@ module Sequel
|
|
821
825
|
schema_hash
|
822
826
|
end
|
823
827
|
|
828
|
+
# Get the array of schema information for the dataset. Returns nil if
|
829
|
+
# the schema information cannot be determined.
|
830
|
+
def get_db_schema_array(reload)
|
831
|
+
check_non_connection_error(false){db.schema(dataset, :reload=>reload)}
|
832
|
+
end
|
833
|
+
|
824
834
|
# Uncached version of setter_methods, to be overridden by plugins
|
825
835
|
# that want to modify the methods used.
|
826
836
|
def get_setter_methods
|
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:
|
@@ -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
|
@@ -63,7 +63,7 @@ module Sequel
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
# :nocov:
|
66
|
-
ruby2_keywords
|
66
|
+
mod.send(:ruby2_keywords, meth) if mod.respond_to?(:ruby2_keywords, true)
|
67
67
|
# :nocov:
|
68
68
|
end
|
69
69
|
|
@@ -97,7 +97,7 @@ module Sequel
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
# :nocov:
|
100
|
-
ruby2_keywords(meth) if respond_to?(:ruby2_keywords,
|
100
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
101
101
|
# :nocov:
|
102
102
|
end
|
103
103
|
|
@@ -129,7 +129,7 @@ module Sequel
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
# :nocov:
|
132
|
-
ruby2_keywords(meth) if respond_to?(:ruby2_keywords,
|
132
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
133
133
|
# :nocov:
|
134
134
|
end
|
135
135
|
|
@@ -159,7 +159,7 @@ module Sequel
|
|
159
159
|
end
|
160
160
|
end
|
161
161
|
# :nocov:
|
162
|
-
ruby2_keywords(meth) if respond_to?(:ruby2_keywords,
|
162
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, true)
|
163
163
|
# :nocov:
|
164
164
|
end
|
165
165
|
|
@@ -109,6 +109,13 @@ module Sequel
|
|
109
109
|
# to eagerly set the associated objects, and having separate threads
|
110
110
|
# modify the same model instance is not thread-safe.
|
111
111
|
#
|
112
|
+
# Because this plugin will automatically use eager loading for
|
113
|
+
# performance, it can break code that defines associations that
|
114
|
+
# do not support eager loading, without marking that they do not
|
115
|
+
# support eager loading via the <tt>allow_eager: false</tt> option.
|
116
|
+
# Make sure to set <tt>allow_eager: false</tt> on any association
|
117
|
+
# used with this plugin if the association doesn't support eager loading.
|
118
|
+
#
|
112
119
|
# Usage:
|
113
120
|
#
|
114
121
|
# # Make all model subclass instances use tactical eager loading (called before loading subclasses)
|
data/lib/sequel/version.rb
CHANGED
@@ -6,11 +6,11 @@ module Sequel
|
|
6
6
|
|
7
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
8
8
|
# release, generally around once a month.
|
9
|
-
MINOR =
|
9
|
+
MINOR = 60
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
13
|
-
TINY =
|
13
|
+
TINY = 1
|
14
14
|
|
15
15
|
# The version of Sequel you are using, as a string (e.g. "2.11.0")
|
16
16
|
VERSION = [MAJOR, MINOR, TINY].join('.').freeze
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.60.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -189,7 +189,9 @@ extra_rdoc_files:
|
|
189
189
|
- doc/release_notes/5.56.0.txt
|
190
190
|
- doc/release_notes/5.57.0.txt
|
191
191
|
- doc/release_notes/5.58.0.txt
|
192
|
+
- doc/release_notes/5.59.0.txt
|
192
193
|
- doc/release_notes/5.6.0.txt
|
194
|
+
- doc/release_notes/5.60.0.txt
|
193
195
|
- doc/release_notes/5.7.0.txt
|
194
196
|
- doc/release_notes/5.8.0.txt
|
195
197
|
- doc/release_notes/5.9.0.txt
|
@@ -275,7 +277,9 @@ files:
|
|
275
277
|
- doc/release_notes/5.56.0.txt
|
276
278
|
- doc/release_notes/5.57.0.txt
|
277
279
|
- doc/release_notes/5.58.0.txt
|
280
|
+
- doc/release_notes/5.59.0.txt
|
278
281
|
- doc/release_notes/5.6.0.txt
|
282
|
+
- doc/release_notes/5.60.0.txt
|
279
283
|
- doc/release_notes/5.7.0.txt
|
280
284
|
- doc/release_notes/5.8.0.txt
|
281
285
|
- doc/release_notes/5.9.0.txt
|
@@ -527,6 +531,7 @@ files:
|
|
527
531
|
- lib/sequel/plugins/prepared_statements.rb
|
528
532
|
- lib/sequel/plugins/prepared_statements_safe.rb
|
529
533
|
- lib/sequel/plugins/rcte_tree.rb
|
534
|
+
- lib/sequel/plugins/require_valid_schema.rb
|
530
535
|
- lib/sequel/plugins/serialization.rb
|
531
536
|
- lib/sequel/plugins/serialization_modification_detection.rb
|
532
537
|
- lib/sequel/plugins/sharding.rb
|