sequel 5.67.0 → 5.74.0

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