sequel 5.67.0 → 5.74.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|