sequel 5.8.0 → 5.9.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 (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)