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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +86 -0
  3. data/README.rdoc +3 -3
  4. data/doc/advanced_associations.rdoc +3 -1
  5. data/doc/mass_assignment.rdoc +1 -1
  6. data/doc/migration.rdoc +15 -0
  7. data/doc/opening_databases.rdoc +8 -1
  8. data/doc/release_notes/5.68.0.txt +61 -0
  9. data/doc/release_notes/5.69.0.txt +26 -0
  10. data/doc/release_notes/5.70.0.txt +35 -0
  11. data/doc/release_notes/5.71.0.txt +21 -0
  12. data/doc/release_notes/5.72.0.txt +33 -0
  13. data/doc/release_notes/5.73.0.txt +66 -0
  14. data/doc/release_notes/5.74.0.txt +45 -0
  15. data/doc/sharding.rdoc +3 -1
  16. data/doc/testing.rdoc +1 -1
  17. data/lib/sequel/adapters/ibmdb.rb +1 -1
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  19. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +4 -0
  20. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  21. data/lib/sequel/adapters/jdbc.rb +10 -6
  22. data/lib/sequel/adapters/mysql.rb +19 -7
  23. data/lib/sequel/adapters/shared/db2.rb +12 -0
  24. data/lib/sequel/adapters/shared/postgres.rb +70 -6
  25. data/lib/sequel/adapters/shared/sqlite.rb +0 -1
  26. data/lib/sequel/adapters/trilogy.rb +117 -0
  27. data/lib/sequel/connection_pool/sharded_threaded.rb +11 -10
  28. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  29. data/lib/sequel/connection_pool/threaded.rb +6 -0
  30. data/lib/sequel/connection_pool/timed_queue.rb +16 -3
  31. data/lib/sequel/connection_pool.rb +8 -1
  32. data/lib/sequel/database/connecting.rb +1 -1
  33. data/lib/sequel/database/schema_methods.rb +4 -3
  34. data/lib/sequel/database/transactions.rb +6 -0
  35. data/lib/sequel/dataset/actions.rb +8 -6
  36. data/lib/sequel/extensions/async_thread_pool.rb +3 -2
  37. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  38. data/lib/sequel/extensions/connection_validator.rb +15 -10
  39. data/lib/sequel/extensions/index_caching.rb +5 -1
  40. data/lib/sequel/extensions/migration.rb +18 -5
  41. data/lib/sequel/extensions/pg_array.rb +9 -1
  42. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  43. data/lib/sequel/extensions/pg_enum.rb +1 -2
  44. data/lib/sequel/extensions/pg_extended_date_support.rb +10 -2
  45. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  46. data/lib/sequel/extensions/pg_multirange.rb +1 -1
  47. data/lib/sequel/extensions/pg_range.rb +1 -1
  48. data/lib/sequel/extensions/pg_row.rb +2 -6
  49. data/lib/sequel/extensions/schema_caching.rb +1 -1
  50. data/lib/sequel/extensions/server_block.rb +2 -1
  51. data/lib/sequel/model/base.rb +20 -10
  52. data/lib/sequel/model/dataset_module.rb +3 -0
  53. data/lib/sequel/model/exceptions.rb +15 -3
  54. data/lib/sequel/plugins/column_encryption.rb +26 -5
  55. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  56. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  57. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  58. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  59. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  60. data/lib/sequel/plugins/paged_operations.rb +181 -0
  61. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +8 -2
  62. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  63. data/lib/sequel/plugins/static_cache.rb +38 -0
  64. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  65. data/lib/sequel/plugins/validation_helpers.rb +8 -1
  66. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  67. data/lib/sequel/version.rb +1 -1
  68. 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 speciying a new schema in new_name will not have an effect.
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
- row[:type] = schema_column_type(row[:db_type])
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 :phrase to specify the function to use to
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 = nil
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 = disconnect_server_connections(server)
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
- if conns
163
- disconnect_connections(conns)
164
- end
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 is one is available within the timeout,
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)