activerecord-jdbc-alt-adapter 61.2.0-java → 70.0.0.rc2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +118 -0
  3. data/.github/workflows/ruby.yml +273 -0
  4. data/.gitignore +1 -0
  5. data/.travis.yml +3 -4
  6. data/Gemfile +9 -7
  7. data/README.md +5 -1
  8. data/Rakefile +1 -1
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +2 -2
  11. data/lib/arel/visitors/compat.rb +5 -33
  12. data/lib/arel/visitors/h2.rb +1 -13
  13. data/lib/arel/visitors/hsqldb.rb +1 -21
  14. data/lib/arel/visitors/sql_server.rb +2 -103
  15. data/lib/arjdbc/abstract/core.rb +8 -9
  16. data/lib/arjdbc/abstract/database_statements.rb +4 -4
  17. data/lib/arjdbc/discover.rb +0 -67
  18. data/lib/arjdbc/hsqldb/adapter.rb +2 -2
  19. data/lib/arjdbc/jdbc/adapter.rb +3 -3
  20. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  21. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  22. data/lib/arjdbc/jdbc/column.rb +1 -26
  23. data/lib/arjdbc/jdbc/type_cast.rb +2 -2
  24. data/lib/arjdbc/jdbc.rb +0 -7
  25. data/lib/arjdbc/mssql/adapter.rb +138 -108
  26. data/lib/arjdbc/mssql/quoting.rb +26 -27
  27. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  28. data/lib/arjdbc/mssql/schema_definitions.rb +32 -17
  29. data/lib/arjdbc/mssql/schema_dumper.rb +13 -1
  30. data/lib/arjdbc/mssql/schema_statements.rb +61 -36
  31. data/lib/arjdbc/mssql/transaction.rb +2 -2
  32. data/lib/arjdbc/mssql/types/date_and_time_types.rb +6 -6
  33. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -2
  34. data/lib/arjdbc/mssql.rb +1 -1
  35. data/lib/arjdbc/mysql/adapter.rb +2 -1
  36. data/lib/arjdbc/oracle/adapter.rb +4 -23
  37. data/lib/arjdbc/postgresql/adapter.rb +152 -4
  38. data/lib/arjdbc/postgresql/oid_types.rb +142 -106
  39. data/lib/arjdbc/sqlite3/adapter.rb +132 -88
  40. data/lib/arjdbc/tasks/database_tasks.rb +0 -12
  41. data/lib/arjdbc/util/serialized_attributes.rb +0 -22
  42. data/lib/arjdbc/util/table_copier.rb +2 -1
  43. data/lib/arjdbc/version.rb +1 -1
  44. data/rakelib/02-test.rake +3 -18
  45. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +17 -2
  46. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +5 -0
  47. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +33 -0
  48. metadata +9 -40
  49. data/lib/active_record/connection_adapters/as400_adapter.rb +0 -2
  50. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -1
  51. data/lib/active_record/connection_adapters/derby_adapter.rb +0 -1
  52. data/lib/active_record/connection_adapters/informix_adapter.rb +0 -1
  53. data/lib/arel/visitors/db2.rb +0 -137
  54. data/lib/arel/visitors/derby.rb +0 -112
  55. data/lib/arel/visitors/firebird.rb +0 -79
  56. data/lib/arjdbc/db2/adapter.rb +0 -808
  57. data/lib/arjdbc/db2/as400.rb +0 -142
  58. data/lib/arjdbc/db2/column.rb +0 -131
  59. data/lib/arjdbc/db2/connection_methods.rb +0 -48
  60. data/lib/arjdbc/db2.rb +0 -4
  61. data/lib/arjdbc/derby/active_record_patch.rb +0 -13
  62. data/lib/arjdbc/derby/adapter.rb +0 -521
  63. data/lib/arjdbc/derby/connection_methods.rb +0 -20
  64. data/lib/arjdbc/derby/schema_creation.rb +0 -15
  65. data/lib/arjdbc/derby.rb +0 -3
  66. data/lib/arjdbc/firebird/adapter.rb +0 -413
  67. data/lib/arjdbc/firebird/connection_methods.rb +0 -23
  68. data/lib/arjdbc/firebird.rb +0 -4
  69. data/lib/arjdbc/informix/adapter.rb +0 -139
  70. data/lib/arjdbc/informix/connection_methods.rb +0 -9
  71. data/lib/arjdbc/sybase/adapter.rb +0 -47
  72. data/lib/arjdbc/sybase.rb +0 -2
  73. data/lib/arjdbc/tasks/db2_database_tasks.rb +0 -104
  74. data/lib/arjdbc/tasks/derby_database_tasks.rb +0 -95
  75. data/src/java/arjdbc/derby/DerbyModule.java +0 -178
  76. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +0 -152
  77. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +0 -174
  78. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +0 -75
@@ -35,14 +35,15 @@ module ActiveRecord
35
35
  ADAPTER_NAME = 'MSSQL'.freeze
36
36
 
37
37
  MSSQL_VERSION_YEAR = {
38
- 8 => '2000',
39
- 9 => '2005',
38
+ 8 => '2000',
39
+ 9 => '2005',
40
40
  10 => '2008',
41
41
  11 => '2012',
42
42
  12 => '2014',
43
43
  13 => '2016',
44
44
  14 => '2017',
45
- 15 => '2019'
45
+ 15 => '2019',
46
+ 16 => '2022'
46
47
  }.freeze
47
48
 
48
49
  include Jdbc::ConnectionPoolCallbacks
@@ -143,13 +144,32 @@ module ActiveRecord
143
144
  def supports_insert_on_conflict?
144
145
  false
145
146
  end
147
+
146
148
  alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
147
149
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
148
150
  alias supports_insert_conflict_target? supports_insert_on_conflict?
149
151
 
152
+ def supports_insert_returning?
153
+ true
154
+ end
155
+
150
156
  def build_insert_sql(insert) # :nodoc:
151
157
  # TODO: hope we can implement an upsert like feature
152
- "INSERT #{insert.into} #{insert.values_list}"
158
+ # "INSERT #{insert.into} #{insert.values_list}"
159
+ sql = +"INSERT #{insert.into}"
160
+
161
+ if returning = insert.send(:insert_all).returning
162
+ returning_sql = if returning.is_a?(String)
163
+ returning
164
+ else
165
+ returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(', ')
166
+ end
167
+
168
+ sql << " OUTPUT #{returning_sql}"
169
+ end
170
+
171
+ sql << " #{insert.values_list}"
172
+ sql
153
173
  end
154
174
 
155
175
  # Overrides abstract method which always returns false
@@ -158,7 +178,7 @@ module ActiveRecord
158
178
  end
159
179
 
160
180
  def clear_cache!
161
- reload_type_map
181
+ # reload_type_map
162
182
  super
163
183
  end
164
184
 
@@ -309,8 +329,121 @@ module ActiveRecord
309
329
  end
310
330
  end
311
331
 
332
+ class << self
333
+ private
334
+
335
+ # This method is called indirectly by the abstract method
336
+ # 'fetch_type_metadata' which then it is called by the java part when
337
+ # calculating a table's columns.
338
+ def initialize_type_map(map)
339
+ # Build the type mapping from SQL Server to ActiveRecord
340
+
341
+ # Integer types.
342
+ map.register_type 'int', MSSQL::Type::Integer.new(limit: 4)
343
+ map.register_type 'tinyint', MSSQL::Type::TinyInteger.new(limit: 1)
344
+ map.register_type 'smallint', MSSQL::Type::SmallInteger.new(limit: 2)
345
+ map.register_type 'bigint', MSSQL::Type::BigInteger.new(limit: 8)
346
+
347
+ # Exact Numeric types.
348
+ map.register_type %r{\Adecimal}i do |sql_type|
349
+ scale = extract_scale(sql_type)
350
+ precision = extract_precision(sql_type)
351
+ if scale == 0
352
+ MSSQL::Type::DecimalWithoutScale.new(precision: precision)
353
+ else
354
+ MSSQL::Type::Decimal.new(precision: precision, scale: scale)
355
+ end
356
+ end
357
+ map.register_type %r{\Amoney\z}i, MSSQL::Type::Money.new
358
+ map.register_type %r{\Asmallmoney\z}i, MSSQL::Type::SmallMoney.new
359
+
360
+ # Approximate Numeric types.
361
+ map.register_type %r{\Afloat\z}i, MSSQL::Type::Float.new
362
+ map.register_type %r{\Areal\z}i, MSSQL::Type::Real.new
363
+
364
+ # Character strings CHAR and VARCHAR (it can become Unicode UTF-8)
365
+ map.register_type 'varchar(max)', MSSQL::Type::VarcharMax.new
366
+ map.register_type %r{\Avarchar\(\d+\)} do |sql_type|
367
+ limit = extract_limit(sql_type)
368
+ MSSQL::Type::Varchar.new(limit: limit)
369
+ end
370
+ map.register_type %r{\Achar\(\d+\)} do |sql_type|
371
+ limit = extract_limit(sql_type)
372
+ MSSQL::Type::Char.new(limit: limit)
373
+ end
374
+
375
+ # Character strings NCHAR and NVARCHAR (by default Unicode UTF-16)
376
+ map.register_type %r{\Anvarchar\(\d+\)} do |sql_type|
377
+ limit = extract_limit(sql_type)
378
+ MSSQL::Type::Nvarchar.new(limit: limit)
379
+ end
380
+ map.register_type %r{\Anchar\(\d+\)} do |sql_type|
381
+ limit = extract_limit(sql_type)
382
+ MSSQL::Type::Nchar.new(limit: limit)
383
+ end
384
+ map.register_type 'nvarchar(max)', MSSQL::Type::NvarcharMax.new
385
+ map.register_type 'nvarchar(4000)', MSSQL::Type::Nvarchar.new
386
+
387
+ # Binary data types.
388
+ map.register_type 'varbinary(max)', MSSQL::Type::VarbinaryMax.new
389
+ register_class_with_limit map, %r{\Abinary\(\d+\)}, MSSQL::Type::BinaryBasic
390
+ register_class_with_limit map, %r{\Avarbinary\(\d+\)}, MSSQL::Type::Varbinary
391
+
392
+ # Miscellaneous types, Boolean, XML, UUID
393
+ # FIXME The xml data needs to be reviewed and fixed
394
+ map.register_type 'bit', MSSQL::Type::Boolean.new
395
+ map.register_type %r{\Auniqueidentifier\z}i, MSSQL::Type::UUID.new
396
+ map.register_type %r{\Axml\z}i, MSSQL::Type::XML.new
397
+
398
+ # Date and time types
399
+ map.register_type 'date', MSSQL::Type::Date.new
400
+ map.register_type 'datetime', MSSQL::Type::DateTime.new
401
+ map.register_type 'smalldatetime', MSSQL::Type::SmallDateTime.new
402
+ register_class_with_precision map, %r{\Atime\(\d+\)}i, MSSQL::Type::Time
403
+ map.register_type 'time(7)', MSSQL::Type::Time.new
404
+ register_class_with_precision map, %r{\Adatetime2\(\d+\)}i, MSSQL::Type::DateTime2
405
+ # map.register_type 'datetime2(7)', MSSQL::Type::DateTime2.new
406
+
407
+ # TODO: we should have identity separated from the sql_type
408
+ # let's say in another attribute (this will help to pass more AR tests),
409
+ # also we add collation attribute per column.
410
+ # aliases
411
+ map.alias_type 'int identity', 'int'
412
+ map.alias_type 'bigint identity', 'bigint'
413
+ map.alias_type 'integer', 'int'
414
+ map.alias_type 'integer', 'int'
415
+ map.alias_type 'INTEGER', 'int'
416
+ map.alias_type 'TINYINT', 'tinyint'
417
+ map.alias_type 'SMALLINT', 'smallint'
418
+ map.alias_type 'BIGINT', 'bigint'
419
+ map.alias_type %r{\Anumeric}i, 'decimal'
420
+ map.alias_type %r{\Anumber}i, 'decimal'
421
+ map.alias_type %r{\Adouble\z}i, 'float'
422
+ map.alias_type 'string', 'nvarchar(4000)'
423
+ map.alias_type %r{\Aboolean\z}i, 'bit'
424
+ map.alias_type 'DATE', 'date'
425
+ map.alias_type 'DATETIME', 'datetime'
426
+ map.alias_type 'SMALLDATETIME', 'smalldatetime'
427
+ map.alias_type %r{\Atime\z}i, 'time(7)'
428
+ map.alias_type %r{\Abinary\z}i, 'varbinary(max)'
429
+ map.alias_type %r{\Ablob\z}i, 'varbinary(max)'
430
+ map.alias_type %r{\Adatetime2\z}i, 'datetime2(7)'
431
+
432
+ # Deprecated SQL Server types.
433
+ map.register_type 'text', MSSQL::Type::Text.new
434
+ map.register_type 'ntext', MSSQL::Type::Ntext.new
435
+ map.register_type 'image', MSSQL::Type::Image.new
436
+ end
437
+ end
438
+
439
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
440
+
312
441
  private
313
442
 
443
+ def type_map
444
+ TYPE_MAP
445
+ end
446
+
314
447
  def translate_exception(exception, message:, sql:, binds:)
315
448
  case message
316
449
  when /no connection available/i
@@ -334,109 +467,6 @@ module ActiveRecord
334
467
  end
335
468
  end
336
469
 
337
- # This method is called indirectly by the abstract method
338
- # 'fetch_type_metadata' which then it is called by the java part when
339
- # calculating a table's columns.
340
- def initialize_type_map(map = type_map)
341
- # Build the type mapping from SQL Server to ActiveRecord
342
-
343
- # Integer types.
344
- map.register_type 'int', MSSQL::Type::Integer.new(limit: 4)
345
- map.register_type 'tinyint', MSSQL::Type::TinyInteger.new(limit: 1)
346
- map.register_type 'smallint', MSSQL::Type::SmallInteger.new(limit: 2)
347
- map.register_type 'bigint', MSSQL::Type::BigInteger.new(limit: 8)
348
-
349
- # Exact Numeric types.
350
- map.register_type %r{\Adecimal}i do |sql_type|
351
- scale = extract_scale(sql_type)
352
- precision = extract_precision(sql_type)
353
- if scale == 0
354
- MSSQL::Type::DecimalWithoutScale.new(precision: precision)
355
- else
356
- MSSQL::Type::Decimal.new(precision: precision, scale: scale)
357
- end
358
- end
359
- map.register_type %r{\Amoney\z}i, MSSQL::Type::Money.new
360
- map.register_type %r{\Asmallmoney\z}i, MSSQL::Type::SmallMoney.new
361
-
362
- # Approximate Numeric types.
363
- map.register_type %r{\Afloat\z}i, MSSQL::Type::Float.new
364
- map.register_type %r{\Areal\z}i, MSSQL::Type::Real.new
365
-
366
- # Character strings CHAR and VARCHAR (it can become Unicode UTF-8)
367
- map.register_type 'varchar(max)', MSSQL::Type::VarcharMax.new
368
- map.register_type %r{\Avarchar\(\d+\)} do |sql_type|
369
- limit = extract_limit(sql_type)
370
- MSSQL::Type::Varchar.new(limit: limit)
371
- end
372
- map.register_type %r{\Achar\(\d+\)} do |sql_type|
373
- limit = extract_limit(sql_type)
374
- MSSQL::Type::Char.new(limit: limit)
375
- end
376
-
377
- # Character strings NCHAR and NVARCHAR (by default Unicode UTF-16)
378
- map.register_type %r{\Anvarchar\(\d+\)} do |sql_type|
379
- limit = extract_limit(sql_type)
380
- MSSQL::Type::Nvarchar.new(limit: limit)
381
- end
382
- map.register_type %r{\Anchar\(\d+\)} do |sql_type|
383
- limit = extract_limit(sql_type)
384
- MSSQL::Type::Nchar.new(limit: limit)
385
- end
386
- map.register_type 'nvarchar(max)', MSSQL::Type::NvarcharMax.new
387
- map.register_type 'nvarchar(4000)', MSSQL::Type::Nvarchar.new
388
-
389
- # Binary data types.
390
- map.register_type 'varbinary(max)', MSSQL::Type::VarbinaryMax.new
391
- register_class_with_limit map, %r{\Abinary\(\d+\)}, MSSQL::Type::BinaryBasic
392
- register_class_with_limit map, %r{\Avarbinary\(\d+\)}, MSSQL::Type::Varbinary
393
-
394
- # Miscellaneous types, Boolean, XML, UUID
395
- # FIXME The xml data needs to be reviewed and fixed
396
- map.register_type 'bit', MSSQL::Type::Boolean.new
397
- map.register_type %r{\Auniqueidentifier\z}i, MSSQL::Type::UUID.new
398
- map.register_type %r{\Axml\z}i, MSSQL::Type::XML.new
399
-
400
- # Date and time types
401
- map.register_type 'date', MSSQL::Type::Date.new
402
- map.register_type 'datetime', MSSQL::Type::DateTime.new
403
- map.register_type 'smalldatetime', MSSQL::Type::SmallDateTime.new
404
- register_class_with_precision map, %r{\Atime\(\d+\)}i, MSSQL::Type::Time
405
- map.register_type 'time(7)', MSSQL::Type::Time.new
406
- register_class_with_precision map, %r{\Adatetime2\(\d+\)}i, MSSQL::Type::DateTime2
407
- map.register_type 'datetime2(7)', MSSQL::Type::DateTime2.new
408
-
409
- # TODO: we should have identity separated from the sql_type
410
- # let's say in another attribute (this will help to pass more AR tests),
411
- # also we add collation attribute per column.
412
- # aliases
413
- map.alias_type 'int identity', 'int'
414
- map.alias_type 'bigint identity', 'bigint'
415
- map.alias_type 'integer', 'int'
416
- map.alias_type 'integer', 'int'
417
- map.alias_type 'INTEGER', 'int'
418
- map.alias_type 'TINYINT', 'tinyint'
419
- map.alias_type 'SMALLINT', 'smallint'
420
- map.alias_type 'BIGINT', 'bigint'
421
- map.alias_type %r{\Anumeric}i, 'decimal'
422
- map.alias_type %r{\Anumber}i, 'decimal'
423
- map.alias_type %r{\Adouble\z}i, 'float'
424
- map.alias_type 'string', 'nvarchar(4000)'
425
- map.alias_type %r{\Aboolean\z}i, 'bit'
426
- map.alias_type 'DATE', 'date'
427
- map.alias_type 'DATETIME', 'datetime'
428
- map.alias_type 'SMALLDATETIME', 'smalldatetime'
429
- map.alias_type %r{\Atime\z}i, 'time(7)'
430
- map.alias_type %r{\Abinary\z}i, 'varbinary(max)'
431
- map.alias_type %r{\Ablob\z}i, 'varbinary(max)'
432
- map.alias_type %r{\Adatetime2\z}i, 'datetime2(7)'
433
-
434
- # Deprecated SQL Server types.
435
- map.register_type 'text', MSSQL::Type::Text.new
436
- map.register_type 'ntext', MSSQL::Type::Ntext.new
437
- map.register_type 'image', MSSQL::Type::Image.new
438
- end
439
-
440
470
  # Returns an array of Column objects for the table specified by +table_name+.
441
471
  # See the concrete implementation for details on the expected parameter values.
442
472
  # NOTE: This is ready, all implemented in the java part of adapter,
@@ -4,8 +4,30 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module MSSQL
6
6
  module Quoting
7
- QUOTED_TRUE = '1'.freeze
8
- QUOTED_FALSE = '0'.freeze
7
+ QUOTED_TRUE = '1'
8
+ QUOTED_FALSE = '0'
9
+
10
+ def quote(value)
11
+ # FIXME: this needs improvements to handle other custom types.
12
+ # Also check if it's possible insert integer into a NVARCHAR
13
+ case value
14
+ when ActiveRecord::Type::Binary::Data
15
+ "0x#{value.hex}"
16
+ # when SomeOtherBinaryData then BLOB_VALUE_MARKER
17
+ # when SomeOtherData then "yyy"
18
+ when String, ActiveSupport::Multibyte::Chars
19
+ "N'#{quote_string(value)}'"
20
+ # when OnlyTimeType then "'#{quoted_time(value)}'"
21
+ when Date, Time
22
+ "'#{quoted_date(value)}'"
23
+ when TrueClass
24
+ quoted_true
25
+ when FalseClass
26
+ quoted_false
27
+ else
28
+ super
29
+ end
30
+ end
9
31
 
10
32
  # Quote date/time values for use in SQL input, includes microseconds
11
33
  # with three digits only if the value is a Time responding to usec.
@@ -15,7 +37,7 @@ module ActiveRecord
15
37
  value = time_with_db_timezone(value)
16
38
  end
17
39
 
18
- result = value.to_s(:db)
40
+ result = value.to_fs(:db)
19
41
 
20
42
  if value.respond_to?(:usec) && value.usec > 0
21
43
  "#{result}.#{sprintf("%06d", value.usec)}"
@@ -105,7 +127,7 @@ module ActiveRecord
105
127
  private
106
128
 
107
129
  def time_with_db_timezone(value)
108
- zone_conv_method = if ActiveRecord::Base.default_timezone == :utc
130
+ zone_conv_method = if ActiveRecord.default_timezone == :utc
109
131
  :getutc
110
132
  else
111
133
  :getlocal
@@ -117,29 +139,6 @@ module ActiveRecord
117
139
  value
118
140
  end
119
141
  end
120
-
121
- # @override
122
- # FIXME: it need to be improved to handle other custom types.
123
- # Also check if it's possible insert integer into a NVARCHAR
124
- def _quote(value)
125
- case value
126
- when ActiveRecord::Type::Binary::Data
127
- "0x#{value.hex}"
128
- # when SomeOtherBinaryData then BLOB_VALUE_MARKER
129
- # when SomeOtherData then "yyy"
130
- when String, ActiveSupport::Multibyte::Chars
131
- "N'#{quote_string(value)}'"
132
- # when OnlyTimeType then "'#{quoted_time(value)}'"
133
- when Date, Time
134
- "'#{quoted_date(value)}'"
135
- when TrueClass
136
- quoted_true
137
- when FalseClass
138
- quoted_false
139
- else
140
- super
141
- end
142
- end
143
142
  end
144
143
  end
145
144
  end
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  end
30
30
 
31
31
  if supports_foreign_keys?
32
- statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
32
+ statements.concat(o.foreign_keys.map { |fk| accept fk })
33
33
  end
34
34
 
35
35
  create_sql << "(#{statements.join(', ')})" if statements.present?
@@ -14,80 +14,95 @@ module ActiveRecord
14
14
 
15
15
  # datetime with seconds always zero (:00) and without fractional seconds
16
16
  def smalldatetime(*args, **options)
17
- args.each { |name| column(name, :smalldatetime, options) }
17
+ args.each { |name| column(name, :smalldatetime, **options) }
18
18
  end
19
19
 
20
20
  # this is the old sql server datetime type, the precision is as follow
21
21
  # xx1, xx3, and xx7
22
22
  def datetime_basic(*args, **options)
23
- args.each { |name| column(name, :datetime_basic, options) }
23
+ args.each { |name| column(name, :datetime_basic, **options) }
24
24
  end
25
25
 
26
26
  def real(*args, **options)
27
- args.each { |name| column(name, :real, options) }
27
+ args.each { |name| column(name, :real, **options) }
28
28
  end
29
29
 
30
30
  def money(*args, **options)
31
- args.each { |name| column(name, :money, options) }
31
+ args.each { |name| column(name, :money, **options) }
32
32
  end
33
33
 
34
34
  def smallmoney(*args, **options)
35
- args.each { |name| column(name, :smallmoney, options) }
35
+ args.each { |name| column(name, :smallmoney, **options) }
36
36
  end
37
37
 
38
38
  def char(*args, **options)
39
- args.each { |name| column(name, :char, options) }
39
+ args.each { |name| column(name, :char, **options) }
40
40
  end
41
41
 
42
42
  def varchar(*args, **options)
43
- args.each { |name| column(name, :varchar, options) }
43
+ args.each { |name| column(name, :varchar, **options) }
44
44
  end
45
45
 
46
46
  def varchar_max(*args, **options)
47
- args.each { |name| column(name, :varchar_max, options) }
47
+ args.each { |name| column(name, :varchar_max, **options) }
48
48
  end
49
49
 
50
50
  def text_basic(*args, **options)
51
- args.each { |name| column(name, :text_basic, options) }
51
+ args.each { |name| column(name, :text_basic, **options) }
52
52
  end
53
53
 
54
54
  def nchar(*args, **options)
55
- args.each { |name| column(name, :nchar, options) }
55
+ args.each { |name| column(name, :nchar, **options) }
56
56
  end
57
57
 
58
58
  def ntext(*args, **options)
59
- args.each { |name| column(name, :ntext, options) }
59
+ args.each { |name| column(name, :ntext, **options) }
60
60
  end
61
61
 
62
62
  def binary_basic(*args, **options)
63
- args.each { |name| column(name, :binary_basic, options) }
63
+ args.each { |name| column(name, :binary_basic, **options) }
64
64
  end
65
65
 
66
66
  def varbinary(*args, **options)
67
- args.each { |name| column(name, :varbinary, options) }
67
+ args.each { |name| column(name, :varbinary, **options) }
68
68
  end
69
69
 
70
70
  def uuid(*args, **options)
71
- args.each { |name| column(name, :uniqueidentifier, options) }
71
+ args.each { |name| column(name, :uniqueidentifier, **options) }
72
72
  end
73
73
  end
74
74
 
75
75
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
76
76
  include ColumnMethods
77
77
 
78
+ def column(name, type, index: nil, **options)
79
+ # TODO: remove this when the below changed is released
80
+ # Fix erroneous nil default precision on virtual datetime columns #46110
81
+ # https://github.com/rails/rails/pull/46110
82
+ #
83
+ if @conn.supports_datetime_with_precision?
84
+ if type == :datetime && !options.key?(:precision)
85
+ options[:precision] = 7
86
+ end
87
+ end
88
+
89
+ super
90
+ end
91
+
92
+
78
93
  def new_column_definition(name, type, **options)
79
94
  case type
80
95
  when :primary_key
81
96
  options[:is_identity] = true
97
+ when :datetime
98
+ options[:precision] = 7 if !options.key?(:precision) && @conn.supports_datetime_with_precision?
82
99
  end
83
100
 
84
101
  super
85
102
  end
86
103
 
87
104
  def timestamps(**options)
88
- if !options.key?(:precision) && @conn.supports_datetime_with_precision?
89
- options[:precision] = 7
90
- end
105
+ options[:precision] = 7 if !options.key?(:precision) && @conn.supports_datetime_with_precision?
91
106
 
92
107
  super
93
108
  end
@@ -28,11 +28,23 @@ module ActiveRecord
28
28
  super && column.identity?
29
29
  end
30
30
 
31
+ def schema_precision(column)
32
+ case column.type
33
+ when :datetime
34
+ if column.precision == 7
35
+ nil
36
+ else
37
+ column.precision.inspect
38
+ end
39
+ else
40
+ super
41
+ end
42
+ end
43
+
31
44
  # def schema_collation(column)
32
45
  # return unless column.collation
33
46
  # column.collation if column.collation != collation
34
47
  # end
35
-
36
48
  end
37
49
  end
38
50
  end
@@ -7,36 +7,36 @@ module ActiveRecord
7
7
 
8
8
  NATIVE_DATABASE_TYPES = {
9
9
  # Logical Rails types to SQL Server types
10
- primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
11
- integer: { name: 'int', limit: 4 },
12
- boolean: { name: 'bit' },
13
- decimal: { name: 'decimal' },
14
- float: { name: 'float' },
15
- date: { name: 'date' },
16
- time: { name: 'time' },
17
- datetime: { name: 'datetime2' },
18
- string: { name: 'nvarchar', limit: 4000 },
19
- text: { name: 'nvarchar(max)' },
20
- binary: { name: 'varbinary(max)' },
10
+ primary_key: 'bigint NOT NULL IDENTITY(1,1) PRIMARY KEY',
11
+ integer: { name: 'int', limit: 4 },
12
+ boolean: { name: 'bit' },
13
+ decimal: { name: 'decimal' },
14
+ float: { name: 'float' },
15
+ date: { name: 'date' },
16
+ time: { name: 'time' },
17
+ datetime: { name: 'datetime2' },
18
+ string: { name: 'nvarchar', limit: 4000 },
19
+ text: { name: 'nvarchar(max)' },
20
+ binary: { name: 'varbinary(max)' },
21
21
  # Other types or SQL Server specific
22
- bigint: { name: 'bigint' },
23
- smalldatetime: { name: 'smalldatetime' },
22
+ bigint: { name: 'bigint' },
23
+ smalldatetime: { name: 'smalldatetime' },
24
24
  datetime_basic: { name: 'datetime' },
25
- timestamp: { name: 'datetime' },
26
- real: { name: 'real' },
27
- money: { name: 'money' },
28
- smallmoney: { name: 'smallmoney' },
29
- char: { name: 'char' },
30
- nchar: { name: 'nchar' },
31
- varchar: { name: 'varchar', limit: 8000 },
32
- varchar_max: { name: 'varchar(max)' },
33
- uuid: { name: 'uniqueidentifier' },
34
- binary_basic: { name: 'binary' },
35
- varbinary: { name: 'varbinary', limit: 8000 },
25
+ timestamp: { name: 'datetime' },
26
+ real: { name: 'real' },
27
+ money: { name: 'money' },
28
+ smallmoney: { name: 'smallmoney' },
29
+ char: { name: 'char' },
30
+ nchar: { name: 'nchar' },
31
+ varchar: { name: 'varchar', limit: 8000 },
32
+ varchar_max: { name: 'varchar(max)' },
33
+ uuid: { name: 'uniqueidentifier' },
34
+ binary_basic: { name: 'binary' },
35
+ varbinary: { name: 'varbinary', limit: 8000 },
36
36
  # Deprecated SQL Server types
37
- image: { name: 'image' },
38
- ntext: { name: 'ntext' },
39
- text_basic: { name: 'text' }
37
+ image: { name: 'image' },
38
+ ntext: { name: 'ntext' },
39
+ text_basic: { name: 'text' }
40
40
  }.freeze
41
41
 
42
42
  def native_database_types
@@ -127,9 +127,17 @@ module ActiveRecord
127
127
  create_database(name, options)
128
128
  end
129
129
 
130
- def remove_column(table_name, column_name, type = nil, options = {})
131
- raise ArgumentError.new('You must specify at least one column name. Example: remove_column(:people, :first_name)') if column_name.is_a? Array
130
+ def remove_columns(table_name, *column_names, type: nil, **options)
131
+ if column_names.empty?
132
+ raise ArgumentError.new('You must specify at least one column name. Example: remove_columns(:people, :first_name)')
133
+ end
134
+
135
+ column_names.each do |column_name|
136
+ remove_column(table_name, column_name, type, **options)
137
+ end
138
+ end
132
139
 
140
+ def remove_column(table_name, column_name, _type = nil, **options)
133
141
  return if options[:if_exists] == true && !column_exists?(table_name, column_name)
134
142
 
135
143
  remove_check_constraints(table_name, column_name)
@@ -138,7 +146,7 @@ module ActiveRecord
138
146
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
139
147
  end
140
148
 
141
- def drop_table(table_name, options = {})
149
+ def drop_table(table_name, **options)
142
150
  # mssql cannot recreate referenced table with force: :cascade
143
151
  # https://docs.microsoft.com/en-us/sql/t-sql/statements/drop-table-transact-sql?view=sql-server-2017
144
152
  if options[:force] == :cascade
@@ -248,7 +256,7 @@ module ActiveRecord
248
256
  (order_columns << super).join(', ')
249
257
  end
250
258
 
251
- def add_timestamps(table_name, options = {})
259
+ def add_timestamps(table_name, **options)
252
260
  if !options.key?(:precision) && supports_datetime_with_precision?
253
261
  options[:precision] = 7
254
262
  end
@@ -256,6 +264,16 @@ module ActiveRecord
256
264
  super
257
265
  end
258
266
 
267
+ def add_column(table_name, column_name, type, **options)
268
+ if supports_datetime_with_precision?
269
+ if type == :datetime && !options.key?(:precision)
270
+ options[:precision] = 7
271
+ end
272
+ end
273
+
274
+ super
275
+ end
276
+
259
277
  def create_schema_dumper(options)
260
278
  MSSQL::SchemaDumper.create(self, options)
261
279
  end
@@ -323,16 +341,20 @@ module ActiveRecord
323
341
  quoted_table = quote_table_name(table_name)
324
342
  quoted_column = quote_column_name(column_name)
325
343
  quoted_default = quote(default)
344
+
326
345
  unless null || default.nil?
327
346
  execute("UPDATE #{quoted_table} SET #{quoted_column}=#{quoted_default} WHERE #{quoted_column} IS NULL")
328
347
  end
348
+
349
+ options = { limit: column.limit, precision: column.precision, scale: column.scale }
350
+
329
351
  sql_alter = [
330
352
  "ALTER TABLE #{quoted_table}",
331
- "ALTER COLUMN #{quoted_column} #{type_to_sql(column.type, limit: column.limit, precision: column.precision, scale: column.scale)}",
332
- (' NOT NULL' unless null)
353
+ "ALTER COLUMN #{quoted_column} #{type_to_sql(column.type, **options)}",
354
+ ('NOT NULL' unless null)
333
355
  ]
334
356
 
335
- execute(sql_alter.join(' '))
357
+ execute(sql_alter.compact.join(' '))
336
358
  end
337
359
 
338
360
  def update_table_definition(table_name, base) #:nodoc:
@@ -345,11 +367,14 @@ module ActiveRecord
345
367
  MSSQL::SchemaCreation.new(self)
346
368
  end
347
369
 
348
- def create_table_definition(*args)
349
- MSSQL::TableDefinition.new(self, *args)
370
+ def create_table_definition(name, **options)
371
+ MSSQL::TableDefinition.new(self, name, **options)
350
372
  end
351
373
 
352
374
  def new_column_from_field(table_name, field)
375
+ # NOTE: this method is used by the columns method in the abstract Class
376
+ # to map column_definitions. It would be good if column_definitions is
377
+ # implemented in ruby
353
378
  field
354
379
  end
355
380