sequel 5.67.0 → 5.74.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 +86 -0
- data/README.rdoc +3 -3
- data/doc/advanced_associations.rdoc +3 -1
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +15 -0
- data/doc/opening_databases.rdoc +8 -1
- 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/sharding.rdoc +3 -1
- data/doc/testing.rdoc +1 -1
- data/lib/sequel/adapters/ibmdb.rb +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +4 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +10 -6
- data/lib/sequel/adapters/mysql.rb +19 -7
- data/lib/sequel/adapters/shared/db2.rb +12 -0
- data/lib/sequel/adapters/shared/postgres.rb +70 -6
- data/lib/sequel/adapters/shared/sqlite.rb +0 -1
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +11 -10
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/threaded.rb +6 -0
- data/lib/sequel/connection_pool/timed_queue.rb +16 -3
- data/lib/sequel/connection_pool.rb +8 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +4 -3
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +8 -6
- data/lib/sequel/extensions/async_thread_pool.rb +3 -2
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +15 -10
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/migration.rb +18 -5
- data/lib/sequel/extensions/pg_array.rb +9 -1
- 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 +10 -2
- data/lib/sequel/extensions/pg_json_ops.rb +52 -0
- data/lib/sequel/extensions/pg_multirange.rb +1 -1
- data/lib/sequel/extensions/pg_range.rb +1 -1
- data/lib/sequel/extensions/pg_row.rb +2 -6
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/server_block.rb +2 -1
- data/lib/sequel/model/base.rb +20 -10
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/exceptions.rb +15 -3
- data/lib/sequel/plugins/column_encryption.rb +26 -5
- data/lib/sequel/plugins/constraint_validations.rb +8 -5
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- 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_auto_constraint_validations.rb +8 -2
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/static_cache.rb +38 -0
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/validation_helpers.rb +8 -1
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/version.rb +1 -1
- metadata +37 -2
@@ -215,6 +215,18 @@ module Sequel
|
|
215
215
|
DATABASE_ERROR_REGEXPS
|
216
216
|
end
|
217
217
|
|
218
|
+
DISCONNECT_SQL_STATES = %w'40003 08001 08003'.freeze
|
219
|
+
def disconnect_error?(exception, opts)
|
220
|
+
sqlstate = database_exception_sqlstate(exception, opts)
|
221
|
+
|
222
|
+
case sqlstate
|
223
|
+
when *DISCONNECT_SQL_STATES
|
224
|
+
true
|
225
|
+
else
|
226
|
+
super
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
218
230
|
# DB2 has issues with quoted identifiers, so
|
219
231
|
# turn off database quoting by default.
|
220
232
|
def quote_identifiers_default
|
@@ -1007,11 +1007,15 @@ module Sequel
|
|
1007
1007
|
SQL::Function.new(:format_type, pg_type[:oid], pg_attribute[:atttypmod]).as(:db_type),
|
1008
1008
|
SQL::Function.new(:pg_get_expr, pg_attrdef[:adbin], pg_class[:oid]).as(:default),
|
1009
1009
|
SQL::BooleanExpression.new(:NOT, pg_attribute[:attnotnull]).as(:allow_null),
|
1010
|
-
SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key)
|
1010
|
+
SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(pg_attribute[:attnum] => SQL::Function.new(:ANY, pg_index[:indkey])), false).as(:primary_key),
|
1011
|
+
Sequel[:pg_type][:typtype],
|
1012
|
+
(~Sequel[Sequel[:elementtype][:oid]=>nil]).as(:is_array),
|
1013
|
+
]}.
|
1011
1014
|
from(:pg_class).
|
1012
1015
|
join(:pg_attribute, :attrelid=>:oid).
|
1013
1016
|
join(:pg_type, :oid=>:atttypid).
|
1014
1017
|
left_outer_join(Sequel[:pg_type].as(:basetype), :oid=>:typbasetype).
|
1018
|
+
left_outer_join(Sequel[:pg_type].as(:elementtype), :typarray=>Sequel[:pg_type][:oid]).
|
1015
1019
|
left_outer_join(:pg_attrdef, :adrelid=>Sequel[:pg_class][:oid], :adnum=>Sequel[:pg_attribute][:attnum]).
|
1016
1020
|
left_outer_join(:pg_index, :indrelid=>Sequel[:pg_class][:oid], :indisprimary=>true).
|
1017
1021
|
where{{pg_attribute[:attisdropped]=>false}}.
|
@@ -1538,11 +1542,12 @@ module Sequel
|
|
1538
1542
|
end
|
1539
1543
|
|
1540
1544
|
# SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
|
1541
|
-
# a rename table operation, so
|
1545
|
+
# a rename table operation, so specifying a new schema in new_name will not have an effect.
|
1542
1546
|
def rename_table_sql(name, new_name)
|
1543
1547
|
"ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
|
1544
1548
|
end
|
1545
1549
|
|
1550
|
+
# Handle interval and citext types.
|
1546
1551
|
def schema_column_type(db_type)
|
1547
1552
|
case db_type
|
1548
1553
|
when /\Ainterval\z/io
|
@@ -1554,6 +1559,43 @@ module Sequel
|
|
1554
1559
|
end
|
1555
1560
|
end
|
1556
1561
|
|
1562
|
+
# The schema :type entry to use for array types.
|
1563
|
+
def schema_array_type(db_type)
|
1564
|
+
:array
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
# The schema :type entry to use for row/composite types.
|
1568
|
+
def schema_composite_type(db_type)
|
1569
|
+
:composite
|
1570
|
+
end
|
1571
|
+
|
1572
|
+
# The schema :type entry to use for enum types.
|
1573
|
+
def schema_enum_type(db_type)
|
1574
|
+
:enum
|
1575
|
+
end
|
1576
|
+
|
1577
|
+
# The schema :type entry to use for range types.
|
1578
|
+
def schema_range_type(db_type)
|
1579
|
+
:range
|
1580
|
+
end
|
1581
|
+
|
1582
|
+
# The schema :type entry to use for multirange types.
|
1583
|
+
def schema_multirange_type(db_type)
|
1584
|
+
:multirange
|
1585
|
+
end
|
1586
|
+
|
1587
|
+
MIN_DATE = Date.new(-4713, 11, 24)
|
1588
|
+
MAX_DATE = Date.new(5874897, 12, 31)
|
1589
|
+
MIN_TIMESTAMP = Time.utc(-4713, 11, 24).freeze
|
1590
|
+
MAX_TIMESTAMP = (Time.utc(294277) - Rational(1, 1000000)).freeze
|
1591
|
+
TYPTYPE_METHOD_MAP = {
|
1592
|
+
'c' => :schema_composite_type,
|
1593
|
+
'e' => :schema_enum_type,
|
1594
|
+
'r' => :schema_range_type,
|
1595
|
+
'm' => :schema_multirange_type,
|
1596
|
+
}
|
1597
|
+
TYPTYPE_METHOD_MAP.default = :schema_column_type
|
1598
|
+
TYPTYPE_METHOD_MAP.freeze
|
1557
1599
|
# The dataset used for parsing table schemas, using the pg_* system catalogs.
|
1558
1600
|
def schema_parse_table(table_name, opts)
|
1559
1601
|
m = output_identifier_meth(opts[:dataset])
|
@@ -1569,11 +1611,33 @@ module Sequel
|
|
1569
1611
|
row.delete(:base_oid)
|
1570
1612
|
row.delete(:db_base_type)
|
1571
1613
|
end
|
1572
|
-
|
1614
|
+
|
1615
|
+
db_type = row[:db_type]
|
1616
|
+
row[:type] = if row.delete(:is_array)
|
1617
|
+
schema_array_type(db_type)
|
1618
|
+
else
|
1619
|
+
send(TYPTYPE_METHOD_MAP[row.delete(:typtype)], db_type)
|
1620
|
+
end
|
1573
1621
|
identity = row.delete(:attidentity)
|
1574
1622
|
if row[:primary_key]
|
1575
1623
|
row[:auto_increment] = !!(row[:default] =~ /\A(?:nextval)/i) || identity == 'a' || identity == 'd'
|
1576
1624
|
end
|
1625
|
+
|
1626
|
+
# :nocov:
|
1627
|
+
if server_version >= 90600
|
1628
|
+
# :nocov:
|
1629
|
+
case row[:oid]
|
1630
|
+
when 1082
|
1631
|
+
row[:min_value] = MIN_DATE
|
1632
|
+
row[:max_value] = MAX_DATE
|
1633
|
+
when 1184, 1114
|
1634
|
+
if Sequel.datetime_class == Time
|
1635
|
+
row[:min_value] = MIN_TIMESTAMP
|
1636
|
+
row[:max_value] = MAX_TIMESTAMP
|
1637
|
+
end
|
1638
|
+
end
|
1639
|
+
end
|
1640
|
+
|
1577
1641
|
[m.call(row.delete(:name)), row]
|
1578
1642
|
end
|
1579
1643
|
end
|
@@ -1681,8 +1745,6 @@ module Sequel
|
|
1681
1745
|
literal_append(sql, args[0])
|
1682
1746
|
sql << ' ' << op.to_s << ' '
|
1683
1747
|
literal_append(sql, args[1])
|
1684
|
-
sql << " ESCAPE "
|
1685
|
-
literal_append(sql, "\\")
|
1686
1748
|
sql << ')'
|
1687
1749
|
else
|
1688
1750
|
super
|
@@ -1736,7 +1798,7 @@ module Sequel
|
|
1736
1798
|
# :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
|
1737
1799
|
# returned rows also include the exact phrase used.
|
1738
1800
|
# :rank :: Set to true to order by the rank, so that closer matches are returned first.
|
1739
|
-
# :to_tsquery :: Can be set to :plain or :
|
1801
|
+
# :to_tsquery :: Can be set to :plain, :phrase, or :websearch to specify the function to use to
|
1740
1802
|
# convert the terms to a ts_query.
|
1741
1803
|
# :tsquery :: Specifies the terms argument is already a valid SQL expression returning a
|
1742
1804
|
# tsquery, and can be used directly in the query.
|
@@ -1756,6 +1818,8 @@ module Sequel
|
|
1756
1818
|
query_func = case to_tsquery = opts[:to_tsquery]
|
1757
1819
|
when :phrase, :plain
|
1758
1820
|
:"#{to_tsquery}to_tsquery"
|
1821
|
+
when :websearch
|
1822
|
+
:"websearch_to_tsquery"
|
1759
1823
|
else
|
1760
1824
|
(opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
|
1761
1825
|
end
|
@@ -504,7 +504,6 @@ module Sequel
|
|
504
504
|
# table_xinfo PRAGMA used, remove hidden columns
|
505
505
|
# that are not generated columns
|
506
506
|
if row[:generated] = (row.delete(:hidden) != 0)
|
507
|
-
next unless row[:type].end_with?(' GENERATED ALWAYS')
|
508
507
|
row[:type] = row[:type].sub(' GENERATED ALWAYS', '')
|
509
508
|
end
|
510
509
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'trilogy'
|
4
|
+
require_relative 'shared/mysql'
|
5
|
+
|
6
|
+
module Sequel
|
7
|
+
module Trilogy
|
8
|
+
class Database < Sequel::Database
|
9
|
+
include Sequel::MySQL::DatabaseMethods
|
10
|
+
|
11
|
+
QUERY_FLAGS = ::Trilogy::QUERY_FLAGS_CAST | ::Trilogy::QUERY_FLAGS_CAST_BOOLEANS
|
12
|
+
LOCAL_TIME_QUERY_FLAGS = QUERY_FLAGS | ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
13
|
+
|
14
|
+
set_adapter_scheme :trilogy
|
15
|
+
|
16
|
+
# Connect to the database. See Trilogy documentation for options.
|
17
|
+
def connect(server)
|
18
|
+
opts = server_opts(server)
|
19
|
+
opts[:username] ||= opts.delete(:user)
|
20
|
+
opts[:found_rows] = true
|
21
|
+
conn = ::Trilogy.new(opts)
|
22
|
+
mysql_connection_setting_sqls.each{|sql| log_connection_yield(sql, conn){conn.query(sql)}}
|
23
|
+
conn
|
24
|
+
end
|
25
|
+
|
26
|
+
def disconnect_connection(c)
|
27
|
+
c.discard!
|
28
|
+
rescue ::Trilogy::Error
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Execute the given SQL on the given connection and yield the result.
|
33
|
+
def execute(sql, opts)
|
34
|
+
r = synchronize(opts[:server]) do |conn|
|
35
|
+
log_connection_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql, conn) do
|
36
|
+
conn.query_with_flags(sql, timezone.nil? || timezone == :local ? LOCAL_TIME_QUERY_FLAGS : QUERY_FLAGS)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
yield r
|
40
|
+
rescue ::Trilogy::Error => e
|
41
|
+
raise_error(e)
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute_dui(sql, opts=OPTS)
|
45
|
+
execute(sql, opts, &:affected_rows)
|
46
|
+
end
|
47
|
+
|
48
|
+
def execute_insert(sql, opts=OPTS)
|
49
|
+
execute(sql, opts, &:last_insert_id)
|
50
|
+
end
|
51
|
+
|
52
|
+
def freeze
|
53
|
+
server_version
|
54
|
+
super
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return the version of the MySQL server to which we are connecting.
|
58
|
+
def server_version(_server=nil)
|
59
|
+
@server_version ||= super()
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def database_specific_error_class(exception, opts)
|
65
|
+
case exception.message
|
66
|
+
when /1205 - Lock wait timeout exceeded; try restarting transaction\z/
|
67
|
+
DatabaseLockTimeout
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def connection_execute_method
|
74
|
+
:query
|
75
|
+
end
|
76
|
+
|
77
|
+
def database_error_classes
|
78
|
+
[::Trilogy::Error]
|
79
|
+
end
|
80
|
+
|
81
|
+
def dataset_class_default
|
82
|
+
Dataset
|
83
|
+
end
|
84
|
+
|
85
|
+
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
|
86
|
+
def schema_column_type(db_type)
|
87
|
+
db_type =~ /\Atinyint\(1\)/ ? :boolean : super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Dataset < Sequel::Dataset
|
92
|
+
include Sequel::MySQL::DatasetMethods
|
93
|
+
|
94
|
+
def fetch_rows(sql)
|
95
|
+
execute(sql) do |r|
|
96
|
+
self.columns = r.fields.map!{|c| output_identifier(c.to_s)}
|
97
|
+
r.each_hash{|h| yield h}
|
98
|
+
end
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def execute(sql, opts=OPTS)
|
105
|
+
opts = Hash[opts]
|
106
|
+
opts[:type] = :select
|
107
|
+
super
|
108
|
+
end
|
109
|
+
|
110
|
+
# Handle correct quoting of strings using ::Trilogy#escape.
|
111
|
+
def literal_string_append(sql, v)
|
112
|
+
sql << "'" << db.synchronize(@opts[:server]){|c| c.escape(v)} << "'"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'threaded'
|
4
4
|
|
5
|
-
# The slowest and most advanced connection, dealing with both multi-threaded
|
5
|
+
# The slowest and most advanced connection pool, dealing with both multi-threaded
|
6
6
|
# access and configurations with multiple shards/servers.
|
7
7
|
#
|
8
8
|
# In addition, this pool subclass also handles scheduling in-use connections
|
@@ -112,7 +112,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
112
112
|
# available, creates a new connection. Passes the connection to the supplied
|
113
113
|
# block:
|
114
114
|
#
|
115
|
-
# pool.hold {|conn| conn.execute('DROP TABLE posts')}
|
115
|
+
# pool.hold(:server1) {|conn| conn.execute('DROP TABLE posts')}
|
116
116
|
#
|
117
117
|
# Pool#hold is re-entrant, meaning it can be called recursively in
|
118
118
|
# the same thread without blocking.
|
@@ -145,12 +145,13 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
145
145
|
# except that after it is used, future requests for the server will use the
|
146
146
|
# :default server instead.
|
147
147
|
def remove_servers(servers)
|
148
|
-
conns =
|
148
|
+
conns = []
|
149
|
+
raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
|
150
|
+
|
149
151
|
sync do
|
150
|
-
raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
|
151
152
|
servers.each do |server|
|
152
153
|
if @servers.include?(server)
|
153
|
-
conns
|
154
|
+
conns.concat(disconnect_server_connections(server))
|
154
155
|
@waiters.delete(server)
|
155
156
|
@available_connections.delete(server)
|
156
157
|
@allocated.delete(server)
|
@@ -159,9 +160,9 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
159
160
|
end
|
160
161
|
end
|
161
162
|
|
162
|
-
|
163
|
-
|
164
|
-
|
163
|
+
nil
|
164
|
+
ensure
|
165
|
+
disconnect_connections(conns)
|
165
166
|
end
|
166
167
|
|
167
168
|
# Return an array of symbols for servers in the connection pool.
|
@@ -186,7 +187,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
186
187
|
# is available. The calling code should NOT already have the mutex when
|
187
188
|
# calling this.
|
188
189
|
#
|
189
|
-
# This should return a connection
|
190
|
+
# This should return a connection if one is available within the timeout,
|
190
191
|
# or nil if a connection could not be acquired within the timeout.
|
191
192
|
def acquire(thread, server)
|
192
193
|
if conn = assign_connection(thread, server)
|
@@ -325,7 +326,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
325
326
|
# Create the maximum number of connections immediately. The calling code should
|
326
327
|
# NOT have the mutex before calling this.
|
327
328
|
def preconnect(concurrent = false)
|
328
|
-
conn_servers = @servers.keys.map!{|s| Array.new(max_size - _size(s), s)}.flatten!
|
329
|
+
conn_servers = sync{@servers.keys}.map!{|s| Array.new(max_size - _size(s), s)}.flatten!
|
329
330
|
|
330
331
|
if concurrent
|
331
332
|
conn_servers.map!{|s| Thread.new{[s, make_new(s)]}}.map!(&:value)
|