sequel 5.8.0 → 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +30 -0
  3. data/doc/release_notes/5.9.0.txt +99 -0
  4. data/doc/testing.rdoc +10 -10
  5. data/lib/sequel/adapters/ado.rb +1 -1
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/jdbc.rb +19 -7
  8. data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -5
  10. data/lib/sequel/adapters/postgres.rb +3 -3
  11. data/lib/sequel/adapters/shared/access.rb +5 -6
  12. data/lib/sequel/adapters/shared/mysql.rb +28 -2
  13. data/lib/sequel/adapters/shared/postgres.rb +16 -6
  14. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  15. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  16. data/lib/sequel/adapters/sqlite.rb +2 -2
  17. data/lib/sequel/connection_pool.rb +2 -1
  18. data/lib/sequel/connection_pool/sharded_threaded.rb +12 -4
  19. data/lib/sequel/connection_pool/threaded.rb +19 -7
  20. data/lib/sequel/core.rb +1 -1
  21. data/lib/sequel/database/connecting.rb +6 -6
  22. data/lib/sequel/database/misc.rb +3 -3
  23. data/lib/sequel/database/query.rb +2 -2
  24. data/lib/sequel/database/schema_generator.rb +9 -3
  25. data/lib/sequel/database/schema_methods.rb +12 -5
  26. data/lib/sequel/dataset/features.rb +5 -0
  27. data/lib/sequel/dataset/misc.rb +1 -1
  28. data/lib/sequel/dataset/prepared_statements.rb +4 -4
  29. data/lib/sequel/dataset/query.rb +5 -0
  30. data/lib/sequel/dataset/sql.rb +8 -6
  31. data/lib/sequel/extensions/escaped_like.rb +100 -0
  32. data/lib/sequel/extensions/eval_inspect.rb +3 -1
  33. data/lib/sequel/extensions/looser_typecasting.rb +3 -3
  34. data/lib/sequel/extensions/pg_extended_date_support.rb +23 -10
  35. data/lib/sequel/model/associations.rb +18 -4
  36. data/lib/sequel/model/base.rb +9 -2
  37. data/lib/sequel/plugins/defaults_setter.rb +1 -1
  38. data/lib/sequel/plugins/many_through_many.rb +1 -1
  39. data/lib/sequel/plugins/nested_attributes.rb +2 -2
  40. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -2
  41. data/lib/sequel/plugins/rcte_tree.rb +5 -7
  42. data/lib/sequel/plugins/sharding.rb +2 -2
  43. data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
  44. data/lib/sequel/plugins/tree.rb +2 -2
  45. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  46. data/lib/sequel/sql.rb +2 -2
  47. data/lib/sequel/version.rb +4 -1
  48. data/spec/adapters/mysql_spec.rb +24 -0
  49. data/spec/adapters/postgres_spec.rb +9 -9
  50. data/spec/adapters/sqlite_spec.rb +10 -10
  51. data/spec/core/connection_pool_spec.rb +22 -0
  52. data/spec/core/database_spec.rb +6 -6
  53. data/spec/core/dataset_spec.rb +16 -5
  54. data/spec/core/expression_filters_spec.rb +1 -1
  55. data/spec/core/schema_spec.rb +1 -1
  56. data/spec/core/version_spec.rb +7 -0
  57. data/spec/extensions/connection_expiration_spec.rb +20 -2
  58. data/spec/extensions/connection_validator_spec.rb +20 -3
  59. data/spec/extensions/escaped_like_spec.rb +40 -0
  60. data/spec/extensions/eval_inspect_spec.rb +1 -1
  61. data/spec/extensions/nested_attributes_spec.rb +6 -0
  62. data/spec/extensions/pg_array_spec.rb +13 -13
  63. data/spec/extensions/pg_auto_constraint_validations_spec.rb +0 -1
  64. data/spec/extensions/pg_range_spec.rb +1 -1
  65. data/spec/extensions/schema_dumper_spec.rb +2 -2
  66. data/spec/extensions/sql_expr_spec.rb +1 -1
  67. data/spec/extensions/string_agg_spec.rb +1 -1
  68. data/spec/extensions/timestamps_spec.rb +2 -2
  69. data/spec/extensions/validation_helpers_spec.rb +1 -1
  70. data/spec/integration/associations_test.rb +12 -0
  71. data/spec/integration/dataset_test.rb +21 -0
  72. data/spec/integration/type_test.rb +4 -4
  73. data/spec/model/base_spec.rb +9 -0
  74. data/spec/model/eager_loading_spec.rb +25 -0
  75. data/spec/model/record_spec.rb +1 -1
  76. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5802a8e3fdb50b3d51f78111c604c86c64f0a3705bcf272423f0b9281800720b
4
- data.tar.gz: 2257a05863d62c70a871032ecc5fdff36196ab717094af98c096570c08f380a7
3
+ metadata.gz: 49e114f3c425c0cdcd798fdcb170ac2112fada51077235f8e53b3d9a3940954a
4
+ data.tar.gz: db61fd6ab89b98c48fd2461f500db1f35afd2624808721733a8efbdc4c14a9ba
5
5
  SHA512:
6
- metadata.gz: 3caabfec31c2953c4f0dd57e68dc58466ff416ab80276ae5ac111fffde7a6dcdddf068b074e33e8df4ea6f41ca941f59f60ec1507d5de2a07ce7b3cdb9016c82
7
- data.tar.gz: 811720c5223a1e2113b47fb0430870dec6377f8d6d1408f0b61dd52abacd596f26af6077c77cf619692c80a43224f06e44633d5c8bd2ee403eafac3a5e7050db
6
+ metadata.gz: ff2793a7d47c0fe5ff7669ef248cd2c66bd1761f36a9ca0d2742adb8ee59be88a12264df0155864e7ac34f2795ff8f1a633d4e97fc87bcd9bb7976e93b67615b
7
+ data.tar.gz: a5996916449312e4a83ecc1e4bdaf2a3789c98321fa622389d675b3b6caf5cc84ce9e413198c2d875d0963ac07b7f3b59f2e80cd50b7eb9f45110b1f87263489
data/CHANGELOG CHANGED
@@ -1,3 +1,33 @@
1
+ === 5.9.0 (2018-06-01)
2
+
3
+ * Support generated columns on MySQL 5.7+ and MariaDB 5.2+ (wjordan, jeremyevans) (#1517)
4
+
5
+ * Add escaped_like extension for creation of LIKE expressions with placeholders in the pattern without access to a dataset (jeremyevans)
6
+
7
+ * Modify jdbc adapter exception handling to work around ::NativeException deprecation in JRuby 9.2 (jeremyevans)
8
+
9
+ * Work around broken BC date handling in JRuby 9.2.0.0 (jeremyevans)
10
+
11
+ * Switch use of BigDecimal.new() to BigDecimal(), since the former is deprecated (jeremyevans)
12
+
13
+ * Add Sequel::VERSION_NUMBER for easier version comparisons (jeremyevans)
14
+
15
+ * Add Model.has_dataset? to determine if the model class has a dataset (AlexWayfer) (#1508)
16
+
17
+ * Support use of LIKE with ANY function on PostgreSQL by avoiding unnecessary use of ESCAPE syntax (jeremyevans)
18
+
19
+ * Disconnect connections left allocated by dead threads instead of returning the connections to the pool (jeremyevans)
20
+
21
+ * Make both threaded connection pools avoid disconnecting connections while holding the connection pool mutex (jeremyevans)
22
+
23
+ * Don't deadlock when disconnecting connections in the sharded_threaded connection pool when using connection_validator or connection_expiration extensions (jeremyevans)
24
+
25
+ * Don't modify hash argument passed in Model.nested_attributes in the nested_attributes plugin (jeremyevans)
26
+
27
+ * Avoid unnecessary hash creation in many places (jeremyevans)
28
+
29
+ * Fix duplicate objects in nested associations when eager_graphing cascaded many_to_one=>one_to_many associations (jeremyevans)
30
+
1
31
  === 5.8.0 (2018-05-01)
2
32
 
3
33
  * Don't mark SQLAnywhere as supporting WITH in INSERT statement (jeremyevans)
@@ -0,0 +1,99 @@
1
+ = New Features
2
+
3
+ * An escaped_like extension has been added, for the creation of
4
+ LIKE/ILIKE expressions with placeholders in patterns without
5
+ access to a dataset. This adds escaped_like and escaped_ilike
6
+ methods to the same Sequel expression objects that support like
7
+ and ilike. These methods take two arguments, the first being
8
+ the pattern, with ? placeholders, and the second being the
9
+ placeholder value (which can be an array for multiple
10
+ placeholders):
11
+
12
+ Sequel.extension :escaped_like
13
+ DB[:table].where{string_column.escaped_like('?%', user_input)}
14
+ # user_input is 'foo':
15
+ # SELECT * FROM table WHERE string_column LIKE 'foo%'
16
+ # user_input is '%foo':
17
+ # SELECT * FROM table WHERE string_column LIKE '\%foo%'
18
+
19
+ * Generated columns on MySQL 5.7+ and MariaDB 5.2+ are now supported
20
+ using the :generated_always_as option when creating the column.
21
+ The :generated_type option can also be used to specify the type of
22
+ generated column (virtual or stored). Examples:
23
+
24
+ DB.add_column :t, :c, Integer, generated_always_as: Sequel[:a]+'b'
25
+ # ALTER TABLE `t` ADD COLUMN `c` varchar(255)
26
+ # GENERATED ALWAYS AS (CONCAT(`a`, 'b'))
27
+
28
+ DB.add_column :t, :c, Integer, generated_always_as: Sequel[:a]+'b',
29
+ generated_type: :virtual
30
+ # ALTER TABLE `t` ADD COLUMN `c` varchar(255)
31
+ # GENERATED ALWAYS AS (CONCAT(`a`, 'b')) VIRTUAL
32
+
33
+ DB.add_column :t, :c, Integer, generated_always_as: Sequel[:a]+'b',
34
+ generated_type: :stored
35
+ # ALTER TABLE `t` ADD COLUMN `c` varchar(255)
36
+ # GENERATED ALWAYS AS (CONCAT(`a`, 'b')) STORED
37
+
38
+ * Sequel::Model.has_dataset? has been added for checking whether the
39
+ model class has an associated dataset. This will generally be true
40
+ for most model classes, but will be false for abstract model
41
+ classes (such as Sequel::Model itself).
42
+
43
+ * Sequel::VERSION_NUMBER has been added for easier future version
44
+ comparisons. The version number for 5.9.0 is 50090.
45
+
46
+ = Other Improvements
47
+
48
+ * When disconnecting connections in the threaded connection pools,
49
+ the disconnection is performed without holding the connection
50
+ pool mutex, since disconnection may block.
51
+
52
+ * The sharded threaded connection pool no longer deadlocks when
53
+ disconnecting connections if the connection_validator or
54
+ connection_expiration extension is used.
55
+
56
+ * If a thread dies and does not check a connection back into the
57
+ connection pool, Sequel now disconnects the connection when it
58
+ detects the dead thread, instead of assuming the connection is
59
+ safe to be reused.
60
+
61
+ * When using eager_graph with cascaded associations, a unique
62
+ object is now used instead of a shared object in cases where
63
+ using a shared object may cause further cascaded associated
64
+ objects to be duplicated.
65
+
66
+ * On PostgreSQL, the ESCAPE modifier to the LIKE/ILIKE operators is
67
+ no longer used, since the default ESCAPE value is the one Sequel
68
+ uses. This change was made in order to allow the LIKE/ILIKE
69
+ operators to work with the ANY function, as PostgreSQL does not
70
+ support the use of the ESCAPE modifier in such cases.
71
+
72
+ * A hash argument passed to Model.nested_attributes in the
73
+ nested_attributes plugin is now no longer modified.
74
+
75
+ * Internal data structures for eager and eager_graph datasets are now
76
+ frozen to avoid unintentional modification.
77
+
78
+ * Nondeterministic behavior in Database#foreign_key_list with the
79
+ :reverse option on PostgreSQL is now avoided by using an
80
+ unambiguous order.
81
+
82
+ * Performance has been improved slightly by avoiding unnecessary
83
+ hash allocations.
84
+
85
+ * Performance has been improved slightly by using while instead
86
+ of Kernel#loop.
87
+
88
+ * BigDecimal() is now used instead of BigDecimal.new(), as the
89
+ latter has been deprecated.
90
+
91
+ * The jdbc adapter now avoids referencing ::NativeException on JRuby
92
+ 9.2+, since JRuby has deprecated it. It is still used on older
93
+ versions of JRuby, since some JRuby 1.7 code may still require it.
94
+
95
+ * Sequel now works around multiple Date/Time conversion bugs in
96
+ JRuby 9.2.0.0 for BC dates in the pg_extended_date_support
97
+ extension. These bugs have already been fixed in JRuby, and
98
+ the workarounds will be removed after the release of JRuby
99
+ 9.2.1.0.
@@ -13,7 +13,7 @@ These run each test in its own transaction, the recommended way to test.
13
13
  require 'minitest/hooks/default'
14
14
  class Minitest::HooksSpec
15
15
  def around
16
- Sequel::Model.db.transaction(:rollback=>:always, :auto_savepoint=>true){super}
16
+ DB.transaction(:rollback=>:always, :auto_savepoint=>true){super}
17
17
  end
18
18
  end
19
19
 
@@ -21,7 +21,7 @@ These run each test in its own transaction, the recommended way to test.
21
21
 
22
22
  class Minitest::Spec
23
23
  def run(*args, &block)
24
- Sequel::Model.db.transaction(:rollback=>:always, :auto_savepoint=>true){super}
24
+ DB.transaction(:rollback=>:always, :auto_savepoint=>true){super}
25
25
  end
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ These run each test in its own transaction, the recommended way to test.
30
30
  # Use this class as the base class for your tests
31
31
  class SequelTestCase < Minitest::Test
32
32
  def run(*args, &block)
33
- Sequel::Model.db.transaction(:rollback=>:always, :auto_savepoint=>true){super}
33
+ DB.transaction(:rollback=>:always, :auto_savepoint=>true){super}
34
34
  end
35
35
  end
36
36
 
@@ -46,7 +46,7 @@ These run each test in its own transaction, the recommended way to test.
46
46
 
47
47
  You can use the Sequel.transaction method to run a transaction on multiple databases, rolling all of them back. Instead of:
48
48
 
49
- Sequel::Model.db.transaction(:rollback=>:always)
49
+ DB.transaction(:rollback=>:always)
50
50
 
51
51
  Use Sequel.transaction with an array of databases:
52
52
 
@@ -66,11 +66,11 @@ Example:
66
66
  require 'minitest/hooks/default'
67
67
  class Minitest::HooksSpec
68
68
  def around
69
- Sequel::Model.db.transaction(:rollback=>:always, :savepoint=>true, :auto_savepoint=>true){super}
69
+ DB.transaction(:rollback=>:always, :savepoint=>true, :auto_savepoint=>true){super}
70
70
  end
71
71
 
72
72
  def around_all
73
- Sequel::Model.db.transaction(:rollback=>:always){super}
73
+ DB.transaction(:rollback=>:always){super}
74
74
  end
75
75
  end
76
76
 
@@ -90,9 +90,9 @@ The order in which you delete/truncate the tables is important if you are using
90
90
 
91
91
  describe "some test suite" do
92
92
  after do
93
- [:table1, :table2].each{|x| Sequel::Model.db.from(x).truncate}
93
+ [:table1, :table2].each{|x| DB.from(x).truncate}
94
94
  # or
95
- [:table1, :table2].each{|x| Sequel::Model.db.from(x).delete}
95
+ [:table1, :table2].each{|x| DB.from(x).delete}
96
96
  end
97
97
  end
98
98
 
@@ -100,9 +100,9 @@ The order in which you delete/truncate the tables is important if you are using
100
100
 
101
101
  class SomeTestClass < Minitest::Test
102
102
  def teardown
103
- [:table1, :table2].each{|x| Sequel::Model.db.from(x).truncate}
103
+ [:table1, :table2].each{|x| DB.from(x).truncate}
104
104
  # or
105
- [:table1, :table2].each{|x| Sequel::Model.db.from(x).delete}
105
+ [:table1, :table2].each{|x| DB.from(x).delete}
106
106
  end
107
107
  end
108
108
 
@@ -54,7 +54,7 @@ module Sequel
54
54
  end
55
55
 
56
56
  def cp.numeric(v)
57
- BigDecimal.new(v)
57
+ BigDecimal(v)
58
58
  end
59
59
 
60
60
  def cp.binary(v)
@@ -30,7 +30,7 @@ module Sequel
30
30
  # Return numeric/decimal types as instances of BigDecimal
31
31
  # instead of Float
32
32
  def decimal(s)
33
- BigDecimal.new(s)
33
+ BigDecimal(s)
34
34
  end
35
35
 
36
36
  # Return datetime types as instances of Sequel.datetime_class
@@ -17,7 +17,19 @@ module Sequel
17
17
  # Contains procs keyed on subadapter type that extend the
18
18
  # given database object so it supports the correct database type.
19
19
  DATABASE_SETUP = {}
20
+
21
+ # Create custom NativeException alias for nicer access, and also so that
22
+ # JRuby 9.2+ so it doesn't use the deprecated ::NativeException
23
+ NativeException = java.lang.Exception
20
24
 
25
+ # Default database error classes
26
+ DATABASE_ERROR_CLASSES = [NativeException]
27
+ if JRUBY_VERSION < '9.2'
28
+ # On JRuby <9.2, still include ::NativeException, as it is still needed in some cases
29
+ DATABASE_ERROR_CLASSES << ::NativeException
30
+ end
31
+ DATABASE_ERROR_CLASSES.freeze
32
+
21
33
  # Allow loading the necessary JDBC support via a gem.
22
34
  def self.load_gem(name)
23
35
  begin
@@ -68,7 +80,7 @@ module Sequel
68
80
  end
69
81
  def RubyBigDecimal(r, i)
70
82
  if v = r.getBigDecimal(i)
71
- BigDecimal.new(v.to_string)
83
+ ::Kernel::BigDecimal(v.to_string)
72
84
  end
73
85
  end
74
86
  def RubyBlob(r, i)
@@ -168,7 +180,7 @@ module Sequel
168
180
  last_insert_id(conn, opts)
169
181
  end
170
182
  end
171
- rescue NativeException, JavaSQL::SQLException => e
183
+ rescue *DATABASE_ERROR_CLASSES => e
172
184
  raise_error(e)
173
185
  ensure
174
186
  cps.close
@@ -189,7 +201,7 @@ module Sequel
189
201
  JavaSQL::DriverManager.setLoginTimeout(opts[:login_timeout]) if opts[:login_timeout]
190
202
  raise StandardError, "skipping regular connection" if opts[:jdbc_properties]
191
203
  JavaSQL::DriverManager.getConnection(*args)
192
- rescue JavaSQL::SQLException, NativeException, StandardError => e
204
+ rescue StandardError, *DATABASE_ERROR_CLASSES => e
193
205
  raise e unless driver
194
206
  # If the DriverManager can't get the connection - use the connect
195
207
  # method of the driver. (This happens under Tomcat for instance)
@@ -203,7 +215,7 @@ module Sequel
203
215
  c = driver.new.connect(args[0], props)
204
216
  raise(Sequel::DatabaseError, 'driver.new.connect returned nil: probably bad JDBC connection string') unless c
205
217
  c
206
- rescue JavaSQL::SQLException, NativeException, StandardError => e2
218
+ rescue StandardError, *DATABASE_ERROR_CLASSES => e2
207
219
  if e2.respond_to?(:message=) && e2.message != e.message
208
220
  e2.message = "#{e2.message}\n#{e.class.name}: #{e.message}"
209
221
  end
@@ -355,7 +367,7 @@ module Sequel
355
367
  end
356
368
 
357
369
  def database_error_classes
358
- [NativeException]
370
+ DATABASE_ERROR_CLASSES
359
371
  end
360
372
 
361
373
  def database_exception_sqlstate(exception, opts)
@@ -436,7 +448,7 @@ module Sequel
436
448
  log_connection_yield(msg, conn, args){cps.executeUpdate}
437
449
  end
438
450
  end
439
- rescue NativeException, JavaSQL::SQLException => e
451
+ rescue *DATABASE_ERROR_CLASSES => e
440
452
  raise_error(e)
441
453
  ensure
442
454
  cps.close unless name
@@ -661,7 +673,7 @@ module Sequel
661
673
  def statement(conn)
662
674
  stmt = conn.createStatement
663
675
  yield stmt
664
- rescue NativeException, JavaSQL::SQLException => e
676
+ rescue *DATABASE_ERROR_CLASSES => e
665
677
  raise_error(e)
666
678
  ensure
667
679
  stmt.close if stmt
@@ -23,7 +23,7 @@ module Sequel
23
23
  if v == JAVA_BIG_DECIMAL_CONSTRUCTOR.call(i)
24
24
  i
25
25
  else
26
- BigDecimal.new(v.to_string)
26
+ ::Kernel::BigDecimal(v.to_string)
27
27
  end
28
28
  end
29
29
  end
@@ -135,11 +135,6 @@ module Sequel
135
135
 
136
136
  private
137
137
 
138
- DATABASE_ERROR_CLASSES = [NativeException].freeze
139
- def database_error_classes
140
- DATABASE_ERROR_CLASSES
141
- end
142
-
143
138
  def disconnect_error?(exception, opts)
144
139
  super || exception.message =~ /\A(This connection has been closed\.|FATAL: terminating connection due to administrator command|An I\/O error occurred while sending to the backend\.)\z/
145
140
  end
@@ -271,7 +271,7 @@ module Sequel
271
271
  def error_info(e)
272
272
  e = e.wrapped_exception if e.is_a?(DatabaseError)
273
273
  r = e.result
274
- h = {
274
+ {
275
275
  :schema => r.error_field(::PG::PG_DIAG_SCHEMA_NAME),
276
276
  :table => r.error_field(::PG::PG_DIAG_TABLE_NAME),
277
277
  :column => r.error_field(::PG::PG_DIAG_COLUMN_NAME),
@@ -449,7 +449,7 @@ module Sequel
449
449
  raise Error, 'calling #listen with :loop requires a block' unless block
450
450
  loop_call = l.respond_to?(:call)
451
451
  catch(:stop) do
452
- loop do
452
+ while true
453
453
  t = timeout_block ? [timeout_block.call] : []
454
454
  conn.wait_for_notify(*t, &block)
455
455
  l.call(conn) if loop_call
@@ -712,7 +712,7 @@ module Sequel
712
712
  yield_hash_rows(res, cols){|h| yield h}
713
713
  return if res.ntuples < rows_per_fetch
714
714
  end
715
- loop do
715
+ while true
716
716
  execute(fetch_sql) do |res|
717
717
  yield_hash_rows(res, cols){|h| yield h}
718
718
  return if res.ntuples < rows_per_fetch
@@ -109,12 +109,6 @@ module Sequel
109
109
  complex_expression_sql_append(sql, :LIKE, args)
110
110
  when :'NOT ILIKE'
111
111
  complex_expression_sql_append(sql, :'NOT LIKE', args)
112
- when :LIKE, :'NOT LIKE'
113
- sql << '('
114
- literal_append(sql, args[0])
115
- sql << ' ' << op.to_s << ' '
116
- literal_append(sql, args[1])
117
- sql << ')'
118
112
  when :'!='
119
113
  sql << '('
120
114
  literal_append(sql, args[0])
@@ -240,6 +234,11 @@ module Sequel
240
234
  end
241
235
  end
242
236
 
237
+ # Access doesn't support ESCAPE for LIKE.
238
+ def requires_like_escape?
239
+ false
240
+ end
241
+
243
242
  # Access requires parentheses when joining more than one table
244
243
  def select_from_sql(sql)
245
244
  if f = @opts[:from]
@@ -19,7 +19,7 @@ module Sequel
19
19
  include Sequel::Database::SplitAlterTable
20
20
 
21
21
  CAST_TYPES = {String=>:CHAR, Integer=>:SIGNED, Time=>:DATETIME, DateTime=>:DATETIME, Numeric=>:DECIMAL, BigDecimal=>:DECIMAL, File=>:BINARY}.freeze
22
- COLUMN_DEFINITION_ORDER = [:collate, :null, :default, :unique, :primary_key, :auto_increment, :references].freeze
22
+ COLUMN_DEFINITION_ORDER = [:generated, :collate, :null, :default, :unique, :primary_key, :auto_increment, :references].freeze
23
23
 
24
24
  # Set the default charset used for CREATE TABLE. You can pass the
25
25
  # :charset option to create_table to override this setting.
@@ -137,6 +137,11 @@ module Sequel
137
137
  true
138
138
  end
139
139
 
140
+ # Generated columns are supported in MariaDB 5.2.0+ and MySQL 5.7.6+.
141
+ def supports_generated_columns?
142
+ server_version >= (mariadb? ? 50200 : 50706)
143
+ end
144
+
140
145
  # MySQL 5+ supports prepared transactions (two-phase commit) using XA
141
146
  def supports_prepared_transactions?
142
147
  server_version >= 50000
@@ -331,6 +336,23 @@ module Sequel
331
336
  end
332
337
  end
333
338
 
339
+ # Add generation clause SQL fragment to column creation SQL.
340
+ def column_definition_generated_sql(sql, column)
341
+ if (generated_expression = column[:generated_always_as])
342
+ sql << " GENERATED ALWAYS AS (#{literal(generated_expression)})"
343
+ case (type = column[:generated_type])
344
+ when nil
345
+ # none, database default
346
+ when :virtual
347
+ sql << " VIRTUAL"
348
+ when :stored
349
+ sql << (mariadb? ? " PERSISTENT" : " STORED")
350
+ else
351
+ raise Error, "unsupported :generated_type option: #{type.inspect}"
352
+ end
353
+ end
354
+ end
355
+
334
356
  def column_definition_order
335
357
  COLUMN_DEFINITION_ORDER
336
358
  end
@@ -473,7 +495,11 @@ module Sequel
473
495
  metadata_dataset.with_sql("DESCRIBE ?", table).map do |row|
474
496
  extra = row.delete(:Extra)
475
497
  if row[:primary_key] = row.delete(:Key) == 'PRI'
476
- row[:auto_increment] = !!(extra.to_s =~ /auto_increment/io)
498
+ row[:auto_increment] = !!(extra.to_s =~ /auto_increment/i)
499
+ end
500
+ if supports_generated_columns?
501
+ # Extra field contains VIRTUAL or PERSISTENT for generated columns
502
+ row[:generated] = !!(extra.to_s =~ /VIRTUAL|STORED|PERSISTENT/i)
477
503
  end
478
504
  row[:allow_null] = row.delete(:Null) == 'YES'
479
505
  row[:default] = row.delete(:Default)