activerecord-sqlserver-adapter 6.1.2.1 → 7.2.4

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +30 -0
  3. data/.devcontainer/boot.sh +22 -0
  4. data/.devcontainer/devcontainer.json +38 -0
  5. data/.devcontainer/docker-compose.yml +42 -0
  6. data/.github/workflows/ci.yml +7 -4
  7. data/.gitignore +3 -1
  8. data/CHANGELOG.md +19 -42
  9. data/Dockerfile.ci +3 -3
  10. data/Gemfile +6 -1
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +113 -27
  13. data/RUNNING_UNIT_TESTS.md +27 -14
  14. data/Rakefile +2 -6
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +3 -3
  17. data/appveyor.yml +4 -6
  18. data/docker-compose.ci.yml +2 -1
  19. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +6 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -23
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +10 -7
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +12 -2
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +24 -16
  26. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  27. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +143 -155
  28. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  29. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +57 -56
  30. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +26 -0
  31. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -12
  32. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +11 -0
  33. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +213 -57
  34. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  35. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +13 -2
  36. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  37. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +19 -1
  38. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +1 -1
  39. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +1 -1
  40. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +1 -1
  41. data/lib/active_record/connection_adapters/sqlserver/utils.rb +21 -10
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +187 -187
  43. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  44. data/lib/active_record/tasks/sqlserver_database_tasks.rb +42 -33
  45. data/lib/arel/visitors/sqlserver.rb +77 -34
  46. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  47. data/test/cases/adapter_test_sqlserver.rb +114 -26
  48. data/test/cases/coerced_tests.rb +1121 -340
  49. data/test/cases/column_test_sqlserver.rb +67 -64
  50. data/test/cases/connection_test_sqlserver.rb +3 -6
  51. data/test/cases/dbconsole.rb +19 -0
  52. data/test/cases/disconnected_test_sqlserver.rb +8 -5
  53. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  54. data/test/cases/enum_test_sqlserver.rb +49 -0
  55. data/test/cases/execute_procedure_test_sqlserver.rb +9 -5
  56. data/test/cases/fetch_test_sqlserver.rb +19 -0
  57. data/test/cases/helper_sqlserver.rb +11 -5
  58. data/test/cases/index_test_sqlserver.rb +8 -6
  59. data/test/cases/json_test_sqlserver.rb +1 -1
  60. data/test/cases/lateral_test_sqlserver.rb +2 -2
  61. data/test/cases/migration_test_sqlserver.rb +19 -1
  62. data/test/cases/optimizer_hints_test_sqlserver.rb +21 -12
  63. data/test/cases/pessimistic_locking_test_sqlserver.rb +8 -7
  64. data/test/cases/primary_keys_test_sqlserver.rb +2 -2
  65. data/test/cases/rake_test_sqlserver.rb +10 -5
  66. data/test/cases/schema_dumper_test_sqlserver.rb +155 -109
  67. data/test/cases/schema_test_sqlserver.rb +64 -1
  68. data/test/cases/showplan_test_sqlserver.rb +7 -7
  69. data/test/cases/specific_schema_test_sqlserver.rb +17 -13
  70. data/test/cases/transaction_test_sqlserver.rb +13 -8
  71. data/test/cases/trigger_test_sqlserver.rb +20 -0
  72. data/test/cases/utils_test_sqlserver.rb +2 -2
  73. data/test/cases/uuid_test_sqlserver.rb +8 -0
  74. data/test/cases/view_test_sqlserver.rb +58 -0
  75. data/test/config.yml +1 -2
  76. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +1 -1
  77. data/test/models/sqlserver/alien.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/trigger.rb +8 -0
  80. data/test/schema/sqlserver_specific_schema.rb +54 -6
  81. data/test/support/coerceable_test_sqlserver.rb +4 -4
  82. data/test/support/connection_reflection.rb +3 -9
  83. data/test/support/core_ext/query_cache.rb +7 -1
  84. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  85. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  86. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  87. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  88. data/test/support/query_assertions.rb +49 -0
  89. data/test/support/rake_helpers.rb +3 -1
  90. data/test/support/table_definition_sqlserver.rb +24 -0
  91. data/test/support/test_in_memory_oltp.rb +2 -2
  92. metadata +41 -17
  93. data/lib/active_record/sqlserver_base.rb +0 -18
  94. data/test/cases/scratchpad_test_sqlserver.rb +0 -8
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  96. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  97. data/test/support/sql_counter_sqlserver.rb +0 -29
@@ -10,8 +10,7 @@ module ActiveRecord
10
10
  class SQLServerDatabaseTasks
11
11
  DEFAULT_COLLATION = "SQL_Latin1_General_CP1_CI_AS"
12
12
 
13
- delegate :connection, :establish_connection, :clear_active_connections!,
14
- to: ActiveRecord::Base
13
+ delegate :with_connection, :establish_connection, to: ActiveRecord::Base
15
14
 
16
15
  def self.using_database_configurations?
17
16
  true
@@ -24,8 +23,10 @@ module ActiveRecord
24
23
 
25
24
  def create(master_established = false)
26
25
  establish_master_connection unless master_established
27
- connection.create_database configuration.database, configuration_hash.merge(collation: default_collation)
28
- establish_connection configuration
26
+ with_connection do |connection|
27
+ connection.create_database(configuration.database, configuration_hash.merge(collation: default_collation))
28
+ end
29
+ establish_connection(configuration)
29
30
  rescue ActiveRecord::StatementInvalid => e
30
31
  if /database .* already exists/i === e.message
31
32
  raise DatabaseAlreadyExists
@@ -36,15 +37,15 @@ module ActiveRecord
36
37
 
37
38
  def drop
38
39
  establish_master_connection
39
- connection.drop_database configuration.database
40
+ with_connection { |connection| connection.drop_database(configuration.database) }
40
41
  end
41
42
 
42
43
  def charset
43
- connection.charset
44
+ with_connection { |connection| connection.charset }
44
45
  end
45
46
 
46
47
  def collation
47
- connection.collation
48
+ with_connection { |connection| connection.collation }
48
49
  end
49
50
 
50
51
  def purge
@@ -53,34 +54,42 @@ module ActiveRecord
53
54
  create true
54
55
  end
55
56
 
56
- def structure_dump(filename, extra_flags)
57
- server_arg = "-S #{Shellwords.escape(configuration_hash[:host])}"
58
- server_arg += ":#{Shellwords.escape(configuration_hash[:port])}" if configuration_hash[:port]
59
- command = [
60
- "defncopy-ttds",
61
- server_arg,
62
- "-D #{Shellwords.escape(configuration_hash[:database])}",
63
- "-U #{Shellwords.escape(configuration_hash[:username])}",
64
- "-P #{Shellwords.escape(configuration_hash[:password])}",
65
- "-o #{Shellwords.escape(filename)}",
66
- ]
67
- table_args = connection.tables.map { |t| Shellwords.escape(t) }
68
- command.concat(table_args)
69
- view_args = connection.views.map { |v| Shellwords.escape(v) }
70
- command.concat(view_args)
71
- raise "Error dumping database" unless Kernel.system(command.join(" "))
72
-
73
- dump = File.read(filename)
74
- dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
75
- dump.gsub!(/^GO\n/, "") # Strip db GO statements
76
- dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
77
- dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
78
- dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
79
- File.open(filename, "w") { |file| file.puts dump }
57
+ def clear_active_connections!
58
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
59
+ end
60
+
61
+ def structure_dump(filename, _extra_flags)
62
+ with_connection do |connection|
63
+ server_arg = "-S #{Shellwords.escape(configuration_hash[:host])}"
64
+ server_arg += ":#{Shellwords.escape(configuration_hash[:port])}" if configuration_hash[:port]
65
+ command = [
66
+ "defncopy-ttds",
67
+ server_arg,
68
+ "-D #{Shellwords.escape(configuration_hash[:database])}",
69
+ "-U #{Shellwords.escape(configuration_hash[:username])}",
70
+ "-P #{Shellwords.escape(configuration_hash[:password])}",
71
+ "-o #{Shellwords.escape(filename)}",
72
+ ]
73
+ table_args = connection.tables.map { |t| Shellwords.escape(t) }
74
+ command.concat(table_args)
75
+ view_args = connection.views.map { |v| Shellwords.escape(v) }
76
+ command.concat(view_args)
77
+ raise "Error dumping database" unless Kernel.system(command.join(" "))
78
+
79
+ dump = File.read(filename)
80
+ dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
81
+ dump.gsub!(/^GO\n/, "") # Strip db GO statements
82
+ dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
83
+ dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
84
+ dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
85
+ File.open(filename, "w") { |file| file.puts dump }
86
+ end
80
87
  end
81
88
 
82
- def structure_load(filename, extra_flags)
83
- connection.execute File.read(filename)
89
+ def structure_load(filename, _extra_flags)
90
+ with_connection do |connection|
91
+ connection.execute File.read(filename)
92
+ end
84
93
  end
85
94
 
86
95
  private
@@ -30,10 +30,46 @@ module Arel
30
30
  end
31
31
 
32
32
  def visit_Arel_Nodes_UpdateStatement(o, collector)
33
- if o.orders.any? && o.limit.nil?
34
- o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
33
+ if has_join_and_composite_primary_key?(o)
34
+ update_statement_using_join(o, collector)
35
+ else
36
+ o.limit = Nodes::Limit.new(9_223_372_036_854_775_807) if o.orders.any? && o.limit.nil?
37
+
38
+ super
35
39
  end
36
- super
40
+ end
41
+
42
+ def visit_Arel_Nodes_DeleteStatement(o, collector)
43
+ if has_join_and_composite_primary_key?(o)
44
+ delete_statement_using_join(o, collector)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def has_join_and_composite_primary_key?(o)
51
+ has_join_sources?(o) && o.relation.left.instance_variable_get(:@klass).composite_primary_key?
52
+ end
53
+
54
+ def delete_statement_using_join(o, collector)
55
+ collector.retryable = false
56
+
57
+ collector << "DELETE "
58
+ visit o.relation.left, collector
59
+ collector << " FROM "
60
+ visit o.relation, collector
61
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
62
+ end
63
+
64
+ def update_statement_using_join(o, collector)
65
+ collector.retryable = false
66
+
67
+ collector << "UPDATE "
68
+ visit o.relation.left, collector
69
+ collect_nodes_for o.values, collector, " SET "
70
+ collector << " FROM "
71
+ visit o.relation, collector
72
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
37
73
  end
38
74
 
39
75
  def visit_Arel_Nodes_Lock(o, collector)
@@ -67,7 +103,7 @@ module Arel
67
103
  def visit_Arel_Nodes_HomogeneousIn(o, collector)
68
104
  collector.preparable = false
69
105
 
70
- collector << quote_table_name(o.table_name) << "." << quote_column_name(o.column_name)
106
+ visit o.left, collector
71
107
 
72
108
  if o.type == :in
73
109
  collector << " IN ("
@@ -77,46 +113,45 @@ module Arel
77
113
 
78
114
  values = o.casted_values
79
115
 
116
+ # Monkey-patch start.
117
+ column_name = o.attribute.name
118
+ column_type = o.attribute.relation.type_for_attribute(column_name)
119
+ column_type = column_type.cast_type if column_type.is_a?(ActiveRecord::Encryption::EncryptedAttributeType) # Use cast_type on encrypted attributes. Don't encrypt them again
120
+
80
121
  if values.empty?
81
122
  collector << @connection.quote(nil)
82
- elsif @connection.prepared_statements
83
- # Monkey-patch start. Add query attribute bindings rather than just values.
84
- column_name = o.column_name
85
- column_type = o.attribute.relation.type_for_attribute(o.column_name)
123
+ elsif @connection.prepared_statements && !column_type.serialized?
124
+ # Add query attribute bindings rather than just values.
86
125
  attrs = values.map { |value| ActiveRecord::Relation::QueryAttribute.new(column_name, value, column_type) }
87
-
88
126
  collector.add_binds(attrs, &bind_block)
89
- # Monkey-patch end.
90
127
  else
91
- collector.add_binds(values, &bind_block)
128
+ collector.add_binds(values, o.proc_for_binds, &bind_block)
92
129
  end
130
+ # Monkey-patch end.
93
131
 
94
132
  collector << ")"
95
- collector
96
133
  end
97
134
 
98
135
  def visit_Arel_Nodes_SelectStatement(o, collector)
99
136
  @select_statement = o
137
+ optimizer_hints = nil
100
138
  distinct_One_As_One_Is_So_Not_Fetch o
101
139
  if o.with
102
140
  collector = visit o.with, collector
103
141
  collector << " "
104
142
  end
105
- collector = o.cores.inject(collector) { |c, x|
106
- visit_Arel_Nodes_SelectCore(x, c)
107
- }
143
+ collector = o.cores.inject(collector) do |collect, core|
144
+ optimizer_hints = core.optimizer_hints if core.optimizer_hints
145
+ visit_Arel_Nodes_SelectCore(core, collect)
146
+ end
108
147
  collector = visit_Orders_And_Let_Fetch_Happen o, collector
109
148
  collector = visit_Make_Fetch_Happen o, collector
149
+ collector = maybe_visit optimizer_hints, collector
110
150
  collector
111
151
  ensure
112
152
  @select_statement = nil
113
153
  end
114
154
 
115
- def visit_Arel_Nodes_SelectCore(o, collector)
116
- collector = super
117
- maybe_visit o.optimizer_hints, collector
118
- end
119
-
120
155
  def visit_Arel_Nodes_OptimizerHints(o, collector)
121
156
  hints = o.expr.map { |v| sanitize_as_option_clause(v) }.join(", ")
122
157
  collector << "OPTION (#{hints})"
@@ -128,10 +163,12 @@ module Arel
128
163
  # github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/450
129
164
  table_name =
130
165
  begin
131
- if o.class.engine.connection.respond_to?(:sqlserver?) && o.class.engine.connection.database_prefix_remote_server?
132
- remote_server_table_name(o)
133
- else
134
- quote_table_name(o.name)
166
+ o.class.engine.with_connection do |connection|
167
+ if connection.respond_to?(:sqlserver?) && connection.database_prefix_remote_server?
168
+ remote_server_table_name(o)
169
+ else
170
+ quote_table_name(o.name)
171
+ end
135
172
  end
136
173
  rescue Exception
137
174
  quote_table_name(o.name)
@@ -200,6 +237,11 @@ module Arel
200
237
  collector
201
238
  end
202
239
 
240
+ def visit_Arel_Nodes_WithRecursive(o, collector)
241
+ collector << "WITH "
242
+ collect_ctes(o.children, collector)
243
+ end
244
+
203
245
  # SQLServer ToSql/Visitor (Additions)
204
246
 
205
247
  def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
@@ -212,7 +254,7 @@ module Arel
212
254
 
213
255
  def visit_Orders_And_Let_Fetch_Happen(o, collector)
214
256
  make_Fetch_Possible_And_Deterministic o
215
- unless o.orders.empty?
257
+ if o.orders.any?
216
258
  collector << " ORDER BY "
217
259
  len = o.orders.length - 1
218
260
  o.orders.each_with_index { |x, i|
@@ -260,15 +302,14 @@ module Arel
260
302
 
261
303
  def make_Fetch_Possible_And_Deterministic(o)
262
304
  return if o.limit.nil? && o.offset.nil?
305
+ return if o.orders.any?
263
306
 
264
307
  t = table_From_Statement o
265
308
  pk = primary_Key_From_Table t
266
309
  return unless pk
267
310
 
268
- if o.orders.empty?
269
- # Prefer deterministic vs a simple `(SELECT NULL)` expr.
270
- o.orders = [pk.asc]
271
- end
311
+ # Prefer deterministic vs a simple `(SELECT NULL)` expr.
312
+ o.orders = [pk.asc]
272
313
  end
273
314
 
274
315
  def distinct_One_As_One_Is_So_Not_Fetch(o)
@@ -315,14 +356,16 @@ module Arel
315
356
  end
316
357
 
317
358
  def remote_server_table_name(o)
318
- ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
319
- "#{o.class.engine.connection.database_prefix}#{o.name}"
320
- ).quoted
359
+ o.class.engine.with_connection do |connection|
360
+ ActiveRecord::ConnectionAdapters::SQLServer::Utils.extract_identifiers(
361
+ "#{connection.database_prefix}#{o.name}"
362
+ ).quoted
363
+ end
321
364
  end
322
365
 
323
- # Need to remove ordering from subqueries unless TOP/OFFSET also used. Otherwise, SQLServer
366
+ # Need to remove ordering from sub-queries unless TOP/OFFSET also used. Otherwise, SQLServer
324
367
  # returns error "The ORDER BY clause is invalid in views, inline functions, derived tables,
325
- # subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
368
+ # sub-queries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified."
326
369
  def remove_invalid_ordering_from_select_statement(node)
327
370
  return unless Arel::Nodes::SelectStatement === node
328
371
 
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cases/helper_sqlserver"
4
+
5
+ class ActiveSchemaTestSQLServer < ActiveRecord::TestCase
6
+ describe "indexes" do
7
+ before do
8
+ connection.create_table :schema_test_table, force: true, id: false do |t|
9
+ t.column :foo, :string, limit: 100
10
+ t.column :state, :string
11
+ end
12
+ end
13
+
14
+ after do
15
+ connection.drop_table :schema_test_table rescue nil
16
+ end
17
+
18
+ it 'default index' do
19
+ assert_queries_match('CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
20
+ connection.add_index :schema_test_table, "foo"
21
+ end
22
+ end
23
+
24
+ it 'unique index' do
25
+ assert_queries_match('CREATE UNIQUE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
26
+ connection.add_index :schema_test_table, "foo", unique: true
27
+ end
28
+ end
29
+
30
+ it 'where condition on index' do
31
+ assert_queries_match("CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo]) WHERE state = 'active'") do
32
+ connection.add_index :schema_test_table, "foo", where: "state = 'active'"
33
+ end
34
+ end
35
+
36
+ it 'if index does not exist' do
37
+ assert_queries_match("IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = 'index_schema_test_table_on_foo') " \
38
+ "CREATE INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])") do
39
+ connection.add_index :schema_test_table, "foo", if_not_exists: true
40
+ end
41
+ end
42
+
43
+ it 'clustered index' do
44
+ assert_queries_match('CREATE CLUSTERED INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
45
+ connection.add_index :schema_test_table, "foo", type: :clustered
46
+ end
47
+ end
48
+
49
+ it 'nonclustered index' do
50
+ assert_queries_match('CREATE NONCLUSTERED INDEX [index_schema_test_table_on_foo] ON [schema_test_table] ([foo])') do
51
+ connection.add_index :schema_test_table, "foo", type: :nonclustered
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'collation' do
57
+ it "create column with NOT NULL and COLLATE" do
58
+ assert_nothing_raised do
59
+ connection.create_table :not_null_with_collation_table, force: true, id: false do |t|
60
+ t.text :not_null_text_with_collation, null: false, collation: "Latin1_General_CS_AS"
61
+ end
62
+ end
63
+ ensure
64
+ connection.drop_table :not_null_with_collation_table rescue nil
65
+ end
66
+ end
67
+
68
+ describe 'datetimeoffset precision' do
69
+ it 'valid precisions are correct' do
70
+ assert_nothing_raised do
71
+ connection.create_table :datetimeoffset_precisions do |t|
72
+ t.datetimeoffset :precision_default
73
+ t.datetimeoffset :precision_5, precision: 5
74
+ t.datetimeoffset :precision_7, precision: 7
75
+ end
76
+ end
77
+
78
+ columns = connection.columns("datetimeoffset_precisions")
79
+
80
+ assert_equal columns.find { |column| column.name == "precision_default" }.precision, 7
81
+ assert_equal columns.find { |column| column.name == "precision_5" }.precision, 5
82
+ assert_equal columns.find { |column| column.name == "precision_7" }.precision, 7
83
+ ensure
84
+ connection.drop_table :datetimeoffset_precisions rescue nil
85
+ end
86
+
87
+ it 'invalid precision raises exception' do
88
+ assert_raise(ActiveRecord::ActiveRecordError) do
89
+ connection.create_table :datetimeoffset_precisions do |t|
90
+ t.datetimeoffset :precision_8, precision: 8
91
+ end
92
+ end
93
+ ensure
94
+ connection.drop_table :datetimeoffset_precisions rescue nil
95
+ end
96
+ end
97
+
98
+ describe 'time precision' do
99
+ it 'valid precisions are correct' do
100
+ assert_nothing_raised do
101
+ connection.create_table :time_precisions do |t|
102
+ t.time :precision_default
103
+ t.time :precision_5, precision: 5
104
+ t.time :precision_7, precision: 7
105
+ end
106
+ end
107
+
108
+ columns = connection.columns("time_precisions")
109
+
110
+ assert_equal columns.find { |column| column.name == "precision_default" }.precision, 7
111
+ assert_equal columns.find { |column| column.name == "precision_5" }.precision, 5
112
+ assert_equal columns.find { |column| column.name == "precision_7" }.precision, 7
113
+ ensure
114
+ connection.drop_table :time_precisions rescue nil
115
+ end
116
+
117
+ it 'invalid precision raises exception' do
118
+ assert_raise(ActiveRecord::ActiveRecordError) do
119
+ connection.create_table :time_precisions do |t|
120
+ t.time :precision_8, precision: 8
121
+ end
122
+ end
123
+ ensure
124
+ connection.drop_table :time_precisions rescue nil
125
+ end
126
+ end
127
+ end