activerecord-sqlserver-adapter-odbc-extended 8.0.10

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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +22 -0
  3. data/.github/workflows/ci.yml +32 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +69 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/Dockerfile.ci +14 -0
  9. data/Gemfile +26 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +104 -0
  12. data/RUNNING_UNIT_TESTS.md +38 -0
  13. data/Rakefile +45 -0
  14. data/VERSION +1 -0
  15. data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
  16. data/compose.ci.yaml +15 -0
  17. data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
  19. data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
  20. data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
  21. data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
  22. data/test/appveyor/dbsetup.ps1 +27 -0
  23. data/test/appveyor/dbsetup.sql +11 -0
  24. data/test/cases/active_schema_test_sqlserver.rb +127 -0
  25. data/test/cases/adapter_test_sqlserver.rb +648 -0
  26. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  27. data/test/cases/change_column_null_test_sqlserver.rb +44 -0
  28. data/test/cases/coerced_tests.rb +2796 -0
  29. data/test/cases/column_test_sqlserver.rb +848 -0
  30. data/test/cases/connection_test_sqlserver.rb +138 -0
  31. data/test/cases/dbconsole.rb +19 -0
  32. data/test/cases/disconnected_test_sqlserver.rb +42 -0
  33. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  34. data/test/cases/enum_test_sqlserver.rb +49 -0
  35. data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
  36. data/test/cases/fetch_test_sqlserver.rb +88 -0
  37. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
  38. data/test/cases/helper_sqlserver.rb +61 -0
  39. data/test/cases/migration_test_sqlserver.rb +144 -0
  40. data/test/cases/order_test_sqlserver.rb +153 -0
  41. data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
  42. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  43. data/test/cases/rake_test_sqlserver.rb +198 -0
  44. data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
  45. data/test/cases/schema_test_sqlserver.rb +111 -0
  46. data/test/cases/trigger_test_sqlserver.rb +51 -0
  47. data/test/cases/utils_test_sqlserver.rb +129 -0
  48. data/test/cases/uuid_test_sqlserver.rb +54 -0
  49. data/test/cases/view_test_sqlserver.rb +58 -0
  50. data/test/config.yml +38 -0
  51. data/test/debug.rb +16 -0
  52. data/test/fixtures/1px.gif +0 -0
  53. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  54. data/test/migrations/create_clients_and_change_column_null.rb +25 -0
  55. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  56. data/test/models/sqlserver/alien.rb +5 -0
  57. data/test/models/sqlserver/booking.rb +5 -0
  58. data/test/models/sqlserver/composite_pk.rb +9 -0
  59. data/test/models/sqlserver/customers_view.rb +5 -0
  60. data/test/models/sqlserver/datatype.rb +5 -0
  61. data/test/models/sqlserver/datatype_migration.rb +10 -0
  62. data/test/models/sqlserver/dollar_table_name.rb +5 -0
  63. data/test/models/sqlserver/edge_schema.rb +13 -0
  64. data/test/models/sqlserver/fk_has_fk.rb +5 -0
  65. data/test/models/sqlserver/fk_has_pk.rb +5 -0
  66. data/test/models/sqlserver/natural_pk_data.rb +6 -0
  67. data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
  68. data/test/models/sqlserver/no_pk_data.rb +5 -0
  69. data/test/models/sqlserver/object_default.rb +5 -0
  70. data/test/models/sqlserver/quoted_table.rb +9 -0
  71. data/test/models/sqlserver/quoted_view_1.rb +5 -0
  72. data/test/models/sqlserver/quoted_view_2.rb +5 -0
  73. data/test/models/sqlserver/sst_memory.rb +5 -0
  74. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  75. data/test/models/sqlserver/string_default.rb +5 -0
  76. data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
  77. data/test/models/sqlserver/string_defaults_view.rb +5 -0
  78. data/test/models/sqlserver/table_with_spaces.rb +5 -0
  79. data/test/models/sqlserver/tinyint_pk.rb +5 -0
  80. data/test/models/sqlserver/trigger.rb +17 -0
  81. data/test/models/sqlserver/trigger_history.rb +5 -0
  82. data/test/models/sqlserver/upper.rb +5 -0
  83. data/test/models/sqlserver/uppered.rb +5 -0
  84. data/test/models/sqlserver/uuid.rb +5 -0
  85. data/test/schema/datatypes/2012.sql +56 -0
  86. data/test/schema/enable-in-memory-oltp.sql +81 -0
  87. data/test/schema/sqlserver_specific_schema.rb +363 -0
  88. data/test/support/coerceable_test_sqlserver.rb +55 -0
  89. data/test/support/connection_reflection.rb +32 -0
  90. data/test/support/core_ext/query_cache.rb +38 -0
  91. data/test/support/load_schema_sqlserver.rb +29 -0
  92. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  93. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  94. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  95. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  96. data/test/support/minitest_sqlserver.rb +3 -0
  97. data/test/support/paths_sqlserver.rb +50 -0
  98. data/test/support/query_assertions.rb +49 -0
  99. data/test/support/rake_helpers.rb +46 -0
  100. data/test/support/table_definition_sqlserver.rb +24 -0
  101. data/test/support/test_in_memory_oltp.rb +17 -0
  102. metadata +240 -0
@@ -0,0 +1,363 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveRecord::Schema.define do
4
+ # Exhaustive Data Types
5
+
6
+ execute File.read(ARTest::SQLServer.schema_datatypes_2012_file)
7
+
8
+ create_table :sst_datatypes_migration, force: true do |t|
9
+ # Simple Rails conventions.
10
+ t.integer :integer_col
11
+ t.bigint :bigint_col
12
+ t.boolean :boolean_col
13
+ t.decimal :decimal_col
14
+ t.float :float_col
15
+ t.string :string_col
16
+ t.text :text_col
17
+ t.datetime :datetime_nil_precision_col, precision: nil
18
+ t.datetime :datetime_col # Precision defaults to 6
19
+ t.timestamp :timestamp_col # Precision defaults to 6
20
+ t.time :time_col
21
+ t.date :date_col
22
+ t.binary :binary_col
23
+ # Our type methods.
24
+ t.real :real_col
25
+ t.money :money_col
26
+ t.smalldatetime :smalldatetime_col
27
+ t.datetime2 :datetime2_col
28
+ t.datetimeoffset :datetimeoffset
29
+ t.smallmoney :smallmoney_col
30
+ t.char :char_col
31
+ t.varchar :varchar_col
32
+ t.text_basic :text_basic_col
33
+ t.nchar :nchar_col
34
+ t.ntext :ntext_col
35
+ t.binary_basic :binary_basic_col
36
+ t.binary_basic :binary_basic_16_col, limit: 16
37
+ t.varbinary :varbinary_col
38
+ t.uuid :uuid_col
39
+ t.ss_timestamp :sstimestamp_col
40
+ if supports_json?
41
+ t.json :json_col
42
+ else
43
+ t.text :json_col
44
+ end
45
+ end
46
+
47
+ # Edge Cases
48
+
49
+ if ENV["IN_MEMORY_OLTP"] && supports_in_memory_oltp?
50
+ create_table "sst_memory", force: true, id: false,
51
+ options: "WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA)" do |t|
52
+ t.primary_key_nonclustered :id
53
+ t.string :name
54
+ t.timestamps
55
+ end
56
+ end
57
+
58
+ create_table "sst_bookings", force: true do |t|
59
+ t.string :name
60
+ t.datetime2 :created_at, null: false
61
+ t.datetime2 :updated_at, null: false
62
+ end
63
+
64
+ create_table "sst_uuids", force: true, id: :uuid do |t|
65
+ t.string :name
66
+ t.uuid :other_uuid, default: "NEWID()"
67
+ t.uuid :uuid_nil_default, default: nil
68
+ end
69
+
70
+ create_table "sst_my$strange_table", force: true do |t|
71
+ t.string :name
72
+ end
73
+
74
+ create_table :SST_UPPER_TESTS, force: true do |t|
75
+ t.column :COLUMN1, :string
76
+ t.column :COLUMN2, :integer
77
+ end
78
+
79
+ create_table :sst_no_pk_data, force: true, id: false do |t|
80
+ t.string :name
81
+ end
82
+
83
+ create_table "sst_quoted-table", force: true do |t|
84
+ end
85
+ execute "IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'sst_quoted-view1') DROP VIEW [sst_quoted-view1]"
86
+ execute "CREATE VIEW [sst_quoted-view1] AS SELECT * FROM [sst_quoted-table]"
87
+ execute "IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'sst_quoted-view2') DROP VIEW [sst_quoted-view2]"
88
+ execute "CREATE VIEW [sst_quoted-view2] AS \n /*#{'x' * 4000}}*/ \n SELECT * FROM [sst_quoted-table]"
89
+
90
+ create_table :sst_string_defaults, force: true do |t|
91
+ t.column :string_with_null_default, :string, default: nil
92
+ t.column :string_with_pretend_null_one, :string, default: "null"
93
+ t.column :string_with_pretend_null_two, :string, default: "(null)"
94
+ t.column :string_with_pretend_null_three, :string, default: "NULL"
95
+ t.column :string_with_pretend_null_four, :string, default: "(NULL)"
96
+ t.column :string_with_pretend_paren_three, :string, default: "(3)"
97
+ t.column :string_with_multiline_default, :string, default: "Some long default with a\nnew line."
98
+ end
99
+
100
+ create_table :sst_string_collation, collation: :SQL_Latin1_General_CP1_CI_AS, force: true do |t|
101
+ t.string :string_without_collation
102
+ t.varchar :string_default_collation, collation: :SQL_Latin1_General_CP1_CI_AS
103
+ t.varchar :string_with_collation, collation: :SQL_Latin1_General_CP1_CS_AS
104
+ t.varchar :varchar_with_collation, collation: :SQL_Latin1_General_CP1_CS_AS
105
+ end
106
+
107
+ create_table :sst_edge_schemas, force: true do |t|
108
+ t.string :description
109
+ t.column "crazy]]quote", :string
110
+ t.column "with spaces", :string
111
+ end
112
+
113
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_natural_pk_data') DROP TABLE sst_natural_pk_data"
114
+ execute <<-NATURALPKTABLESQL
115
+ CREATE TABLE sst_natural_pk_data(
116
+ parent_id int,
117
+ name nvarchar(255),
118
+ description nvarchar(1000),
119
+ legacy_id nvarchar(10) NOT NULL PRIMARY KEY
120
+ )
121
+ NATURALPKTABLESQL
122
+
123
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_natural_pk_int_data') DROP TABLE sst_natural_pk_int_data"
124
+ execute <<-NATURALPKINTTABLESQL
125
+ CREATE TABLE sst_natural_pk_int_data(
126
+ legacy_id int NOT NULL PRIMARY KEY,
127
+ parent_id int,
128
+ name nvarchar(255),
129
+ description nvarchar(1000)
130
+ )
131
+ NATURALPKINTTABLESQL
132
+
133
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_tinyint_pk') DROP TABLE sst_tinyint_pk"
134
+ execute <<-TINYITPKTABLE
135
+ CREATE TABLE sst_tinyint_pk(
136
+ id tinyint IDENTITY NOT NULL PRIMARY KEY,
137
+ name nvarchar(255)
138
+ )
139
+ TINYITPKTABLE
140
+
141
+ execute "DROP DEFAULT [sst_getdateobject];" rescue nil
142
+ execute "CREATE DEFAULT [sst_getdateobject] AS getdate();" rescue nil
143
+ create_table "sst_defaultobjects", force: true do |t|
144
+ t.string :name
145
+ t.date :date
146
+ end
147
+ execute "sp_bindefault 'sst_getdateobject', 'sst_defaultobjects.date'"
148
+
149
+ execute "DROP PROCEDURE my_getutcdate" rescue nil
150
+ execute <<-SQL
151
+ CREATE PROCEDURE my_getutcdate AS
152
+ SELECT GETUTCDATE() utcdate
153
+ SQL
154
+
155
+ create_table 'A Table With Spaces', force: true do |t|
156
+ t.string :name
157
+ end
158
+
159
+ # Constraints
160
+
161
+ create_table(:sst_has_fks, force: true) do |t|
162
+ t.column(:fk_id, :bigint, null: false)
163
+ t.column(:fk_id2, :bigint)
164
+ end
165
+
166
+ create_table(:sst_has_pks, force: true) {}
167
+ execute <<-ADDFKSQL
168
+ ALTER TABLE sst_has_fks
169
+ ADD CONSTRAINT FK__sst_has_fks_id
170
+ FOREIGN KEY ([fk_id])
171
+ REFERENCES [sst_has_pks] ([id]),
172
+
173
+ CONSTRAINT FK__sst_has_fks_id2
174
+ FOREIGN KEY ([fk_id2])
175
+ REFERENCES [sst_has_pks] ([id])
176
+ ADDFKSQL
177
+
178
+ # Views
179
+
180
+ execute "IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'sst_customers_view') DROP VIEW sst_customers_view"
181
+ execute <<-CUSTOMERSVIEW
182
+ CREATE VIEW sst_customers_view AS
183
+ SELECT id, name, balance
184
+ FROM customers
185
+ CUSTOMERSVIEW
186
+
187
+ execute "IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'sst_string_defaults_view') DROP VIEW sst_string_defaults_view"
188
+ execute <<-STRINGDEFAULTSVIEW
189
+ CREATE VIEW sst_string_defaults_view AS
190
+ SELECT id, string_with_pretend_null_one as pretend_null
191
+ FROM sst_string_defaults
192
+ STRINGDEFAULTSVIEW
193
+
194
+ execute "IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'sst_string_defaults_big_view') DROP VIEW sst_string_defaults_big_view"
195
+ execute <<-STRINGDEFAULTSBIGVIEW
196
+ CREATE VIEW sst_string_defaults_big_view AS
197
+ SELECT id, string_with_pretend_null_one as pretend_null
198
+ /*#{'x' * 4000}}*/
199
+ FROM sst_string_defaults
200
+ STRINGDEFAULTSBIGVIEW
201
+
202
+ # Trigger
203
+
204
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_trigger') DROP TABLE sst_table_with_trigger"
205
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_trigger_history') DROP TABLE sst_table_with_trigger_history"
206
+ execute <<-SQL
207
+ CREATE TABLE sst_table_with_trigger(
208
+ id bigint IDENTITY NOT NULL PRIMARY KEY,
209
+ event_name nvarchar(255)
210
+ )
211
+ CREATE TABLE sst_table_with_trigger_history(
212
+ id bigint IDENTITY NOT NULL PRIMARY KEY,
213
+ id_source nvarchar(36),
214
+ event_name nvarchar(255)
215
+ )
216
+ SQL
217
+ execute <<-SQL
218
+ CREATE TRIGGER sst_table_with_trigger_t ON sst_table_with_trigger
219
+ FOR INSERT
220
+ AS
221
+ INSERT INTO sst_table_with_trigger_history (id_source, event_name)
222
+ SELECT id AS id_source, event_name FROM INSERTED
223
+ SQL
224
+
225
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_uuid_trigger') DROP TABLE sst_table_with_uuid_trigger"
226
+ execute <<-SQL
227
+ CREATE TABLE sst_table_with_uuid_trigger(
228
+ id uniqueidentifier DEFAULT NEWID() PRIMARY KEY,
229
+ event_name nvarchar(255)
230
+ )
231
+ SQL
232
+ execute <<-SQL
233
+ CREATE TRIGGER sst_table_with_uuid_trigger_t ON sst_table_with_uuid_trigger
234
+ FOR INSERT
235
+ AS
236
+ INSERT INTO sst_table_with_trigger_history (id_source, event_name)
237
+ SELECT id AS id_source, event_name FROM INSERTED
238
+ SQL
239
+
240
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_composite_pk_trigger') DROP TABLE sst_table_with_composite_pk_trigger"
241
+ execute <<-SQL
242
+ CREATE TABLE sst_table_with_composite_pk_trigger(
243
+ pk_col_one int NOT NULL,
244
+ pk_col_two int NOT NULL,
245
+ event_name nvarchar(255),
246
+ CONSTRAINT PK_sst_table_with_composite_pk_trigger PRIMARY KEY (pk_col_one, pk_col_two)
247
+ )
248
+ SQL
249
+ execute <<-SQL
250
+ CREATE TRIGGER sst_table_with_composite_pk_trigger_t ON sst_table_with_composite_pk_trigger
251
+ FOR INSERT
252
+ AS
253
+ INSERT INTO sst_table_with_trigger_history (id_source, event_name)
254
+ SELECT pk_col_one AS id_source, event_name FROM INSERTED
255
+ SQL
256
+
257
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_table_with_composite_pk_trigger_with_different_data_type') DROP TABLE sst_table_with_composite_pk_trigger_with_different_data_type"
258
+ execute <<-SQL
259
+ CREATE TABLE sst_table_with_composite_pk_trigger_with_different_data_type(
260
+ pk_col_one uniqueidentifier DEFAULT NEWID(),
261
+ pk_col_two int NOT NULL,
262
+ event_name nvarchar(255),
263
+ CONSTRAINT PK_sst_table_with_composite_pk_trigger_with_different_data_type PRIMARY KEY (pk_col_one, pk_col_two)
264
+ )
265
+ SQL
266
+ execute <<-SQL
267
+ CREATE TRIGGER sst_table_with_composite_pk_trigger_with_different_data_type_t ON sst_table_with_composite_pk_trigger_with_different_data_type
268
+ FOR INSERT
269
+ AS
270
+ INSERT INTO sst_table_with_trigger_history (id_source, event_name)
271
+ SELECT pk_col_one AS id_source, event_name FROM INSERTED
272
+ SQL
273
+
274
+ # Another schema.
275
+
276
+ create_table :sst_schema_columns, force: true do |t|
277
+ t.column :field1, :integer
278
+ end
279
+
280
+ execute "IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = 'test') EXEC sp_executesql N'CREATE SCHEMA test'"
281
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_columns' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_columns"
282
+ execute <<-SIMILIARTABLEINOTHERSCHEMA
283
+ CREATE TABLE test.sst_schema_columns(
284
+ id int IDENTITY NOT NULL primary key,
285
+ filed_1 int,
286
+ field_2 int,
287
+ name varchar(255),
288
+ description varchar(1000),
289
+ n_name nvarchar(255),
290
+ n_description nvarchar(1000)
291
+ )
292
+ SIMILIARTABLEINOTHERSCHEMA
293
+
294
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_identity' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_identity"
295
+ execute <<-SIMILIARTABLEINOTHERSCHEMA
296
+ CREATE TABLE test.sst_schema_identity(
297
+ id int IDENTITY NOT NULL primary key,
298
+ filed_1 int
299
+ )
300
+ SIMILIARTABLEINOTHERSCHEMA
301
+
302
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_natural_id' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_natural_id"
303
+ execute <<-NATURALPKTABLESQLINOTHERSCHEMA
304
+ CREATE TABLE test.sst_schema_natural_id(
305
+ parent_id int,
306
+ name nvarchar(255),
307
+ description nvarchar(1000),
308
+ legacy_id nvarchar(10) NOT NULL PRIMARY KEY,
309
+ )
310
+ NATURALPKTABLESQLINOTHERSCHEMA
311
+
312
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_test_multiple_schema' and TABLE_SCHEMA = 'test') DROP TABLE test.sst_schema_test_multiple_schema"
313
+ execute <<-SCHEMATESTMULTIPLESCHEMA
314
+ CREATE TABLE test.sst_schema_test_multiple_schema(
315
+ field_1 int NOT NULL PRIMARY KEY,
316
+ field_2 int,
317
+ )
318
+ SCHEMATESTMULTIPLESCHEMA
319
+ execute "IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = 'test2') EXEC sp_executesql N'CREATE SCHEMA test2'"
320
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_schema_test_multiple_schema' and TABLE_SCHEMA = 'test2') DROP TABLE test2.sst_schema_test_multiple_schema"
321
+ execute <<-SCHEMATESTMULTIPLESCHEMA
322
+ CREATE TABLE test2.sst_schema_test_multiple_schema(
323
+ field_1 int,
324
+ field_2 int NOT NULL PRIMARY KEY,
325
+ )
326
+ SCHEMATESTMULTIPLESCHEMA
327
+
328
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'unique_key_dumped_table') DROP TABLE unique_key_dumped_table"
329
+ execute <<-SQLSERVERUNIQUEKEYS
330
+ CREATE TABLE unique_key_dumped_table (
331
+ id int IDENTITY(1,1) NOT NULL,
332
+ unique_field int DEFAULT 0 NOT NULL,
333
+ CONSTRAINT IX_UNIQUE_KEY UNIQUE (unique_field),
334
+ CONSTRAINT PK_UNIQUE_KEY PRIMARY KEY (id)
335
+ );
336
+ SQLSERVERUNIQUEKEYS
337
+
338
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_composite_without_identity') DROP TABLE sst_composite_without_identity"
339
+ execute <<-COMPOSITE_WITHOUT_IDENTITY
340
+ CREATE TABLE sst_composite_without_identity (
341
+ pk_col_one int NOT NULL,
342
+ pk_col_two int NOT NULL,
343
+ CONSTRAINT PK_sst_composite_without_identity PRIMARY KEY (pk_col_one, pk_col_two)
344
+ );
345
+ COMPOSITE_WITHOUT_IDENTITY
346
+
347
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sst_composite_with_identity') DROP TABLE sst_composite_with_identity"
348
+ execute <<-COMPOSITE_WITH_IDENTITY
349
+ CREATE TABLE sst_composite_with_identity (
350
+ pk_col_one int IDENTITY NOT NULL,
351
+ pk_col_two int NOT NULL,
352
+ CONSTRAINT PK_sst_composite_with_identity PRIMARY KEY (pk_col_one, pk_col_two)
353
+ );
354
+ COMPOSITE_WITH_IDENTITY
355
+
356
+ execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'aliens' and TABLE_SCHEMA = 'test') DROP TABLE test.aliens"
357
+ execute <<-TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL
358
+ CREATE TABLE test.aliens(
359
+ id int IDENTITY NOT NULL primary key,
360
+ name varchar(255)
361
+ )
362
+ TABLE_IN_OTHER_SCHEMA_USED_BY_MODEL
363
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ARTest
4
+ module SQLServer
5
+ module CoerceableTest
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ cattr_accessor :coerced_tests, instance_accessor: false
10
+ self.coerced_tests = []
11
+ end
12
+
13
+ module ClassMethods
14
+ def coerce_tests!(*methods)
15
+ methods.each do |method|
16
+ coerced_tests.push(method)
17
+ coerced_test_warning(method)
18
+ end
19
+ end
20
+
21
+ def coerce_all_tests!
22
+ instance_methods(false).each do |method|
23
+ next unless method.to_s =~ /\Atest/
24
+
25
+ undef_method(method)
26
+ end
27
+ STDOUT.puts "🙉 🙈 🙊 Undefined all tests: #{name}"
28
+ end
29
+
30
+ private
31
+
32
+ def coerced_test_warning(test_to_coerce)
33
+ if test_to_coerce.is_a?(Regexp)
34
+ method = instance_methods(false).select { |m| m =~ test_to_coerce }
35
+ else
36
+ method = test_to_coerce
37
+ end
38
+
39
+ Array(method).each do |m|
40
+ result = if m && method_defined?(m)
41
+ alias_method("original_#{test_to_coerce.inspect.tr('/\:"', '')}", m)
42
+ undef_method(m)
43
+ end
44
+
45
+ if result.blank?
46
+ STDOUT.puts "🐳 Unfound coerced test: #{name}##{m}"
47
+ else
48
+ STDOUT.puts "🐵 Undefined coerced test: #{name}##{m}"
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ARTest
4
+ module SQLServer
5
+ module ConnectionReflection
6
+ extend ActiveSupport::Concern
7
+
8
+ included { extend ConnectionReflection }
9
+
10
+ def connection
11
+ ActiveRecord::Base.lease_connection
12
+ end
13
+
14
+ def connection_options
15
+ connection.instance_variable_get :@connection_parameters
16
+ end
17
+
18
+ def connection_tds_73
19
+ rc = connection.raw_connection
20
+ rc.respond_to?(:tds_73?) && rc.tds_73?
21
+ end
22
+
23
+ def connection_odbc?
24
+ connection_options[:mode] == :odbc
25
+ end
26
+
27
+ def connection_sqlserver_azure?
28
+ connection.sqlserver_azure?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/sqlserver_adapter"
4
+
5
+ module SqlIgnoredCache
6
+ extend ActiveSupport::Concern
7
+
8
+ IGNORED_SQL = [
9
+ /INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS|KEY_COLUMN_USAGE)/im,
10
+ /sys.columns/i,
11
+ /SELECT @@version/,
12
+ /SELECT @@TRANCOUNT/,
13
+ /(BEGIN|COMMIT|ROLLBACK|SAVE) TRANSACTION/,
14
+ /SELECT CAST\(.* AS .*\) AS value/,
15
+ /SELECT DATABASEPROPERTYEX/im
16
+ ]
17
+
18
+ # We don't want to coerce every ActiveRecord test that relies on `query_cache`
19
+ # just because we do more queries than the other adapters.
20
+ #
21
+ # Removing internal queries from the cache will make AR tests pass without
22
+ # compromising cache outside tests.
23
+ def cache_sql(sql, name, binds)
24
+ result = super
25
+
26
+ @query_cache.instance_variable_get(:@map).delete_if do |cache_key, _v|
27
+ # Query cache key generated by `sql` or `[sql, binds]`, so need to retrieve `sql` for both cases.
28
+ cache_key_sql = Array(cache_key).first
29
+ Regexp.union(IGNORED_SQL).match?(cache_key_sql)
30
+ end
31
+
32
+ result
33
+ end
34
+ end
35
+
36
+ ActiveSupport.on_load(:active_record) do
37
+ ActiveRecord::ConnectionAdapters::SQLServerAdapter.prepend(SqlIgnoredCache)
38
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ARTest
4
+ module SQLServer
5
+ extend self
6
+
7
+ def schema_root
8
+ File.join ARTest::SQLServer.test_root_sqlserver, "schema"
9
+ end
10
+
11
+ def schema_file
12
+ File.join schema_root, "sqlserver_specific_schema.rb"
13
+ end
14
+
15
+ def schema_datatypes_2012_file
16
+ File.join schema_root, "datatypes", "2012.sql"
17
+ end
18
+
19
+ def load_schema
20
+ original_stdout = $stdout
21
+ $stdout = StringIO.new
22
+ load schema_file
23
+ ensure
24
+ $stdout = original_stdout
25
+ end
26
+ end
27
+ end
28
+
29
+ ARTest::SQLServer.load_schema
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest-spec-rails/init/active_support"
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ARTest
4
+ module SQLServer
5
+ extend self
6
+
7
+ def root_sqlserver
8
+ File.expand_path File.join(File.dirname(__FILE__), "..", "..")
9
+ end
10
+
11
+ def test_root_sqlserver
12
+ File.join root_sqlserver, "test"
13
+ end
14
+
15
+ def root_activerecord
16
+ File.join Gem.loaded_specs["rails"].full_gem_path, "activerecord"
17
+ end
18
+
19
+ def root_activerecord_lib
20
+ File.join root_activerecord, "lib"
21
+ end
22
+
23
+ def root_activerecord_test
24
+ File.join root_activerecord, "test"
25
+ end
26
+
27
+ def test_load_paths
28
+ ["lib", "test", root_activerecord_lib, root_activerecord_test]
29
+ end
30
+
31
+ def add_to_load_paths!
32
+ test_load_paths.each { |p| $LOAD_PATH.unshift(p) unless $LOAD_PATH.include?(p) }
33
+ end
34
+
35
+ def migrations_root
36
+ File.join test_root_sqlserver, "migrations"
37
+ end
38
+
39
+ def arconfig_file
40
+ File.join test_root_sqlserver, "config.yml"
41
+ end
42
+
43
+ def arconfig_file_env!
44
+ ENV["ARCONFIG"] = arconfig_file
45
+ end
46
+ end
47
+ end
48
+
49
+ ARTest::SQLServer.add_to_load_paths!
50
+ ARTest::SQLServer.arconfig_file_env!
@@ -0,0 +1,49 @@
1
+ module ARTest
2
+ module SQLServer
3
+ module QueryAssertions
4
+ def assert_queries_count(count = nil, include_schema: false, &block)
5
+ ActiveRecord::Base.lease_connection.materialize_transactions
6
+
7
+ counter = ActiveRecord::Assertions::QueryAssertions::SQLCounter.new
8
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
9
+ result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
10
+ queries = include_schema ? counter.log_all : counter.log
11
+
12
+ # Start of monkey-patch
13
+ queries = include_release_savepoint_placeholder_queries(queries)
14
+ # End of monkey-patch
15
+
16
+ if count
17
+ assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
18
+ else
19
+ assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
20
+ end
21
+ result
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # Rails tests expect a save-point to be created and released. SQL Server does not release
28
+ # save-points and so the number of queries will be off. This monkey patch adds a placeholder queries
29
+ # to replace the missing save-point releases.
30
+ def include_release_savepoint_placeholder_queries(queries)
31
+ grouped_queries = [[]]
32
+
33
+ queries.each do |query|
34
+ if query =~ /SAVE TRANSACTION \S+/
35
+ grouped_queries << [query]
36
+ else
37
+ grouped_queries.last << query
38
+ end
39
+ end
40
+
41
+ grouped_queries.each do |group|
42
+ group.append "/* release savepoint placeholder for testing */" if group.first =~ /SAVE TRANSACTION \S+/
43
+ end
44
+
45
+ grouped_queries.flatten
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ SQLSERVER_HELPER = "test/cases/helper_sqlserver.rb"
4
+ SQLSERVER_COERCED = "test/cases/coerced_tests.rb"
5
+
6
+ def env_ar_test_files
7
+ return unless ENV["TEST_FILES_AR"] && !ENV["TEST_FILES_AR"].empty?
8
+
9
+ @env_ar_test_files ||= begin
10
+ ENV["TEST_FILES_AR"].split(",").map { |file|
11
+ File.join ARTest::SQLServer.root_activerecord, file.strip
12
+ }.sort
13
+ end
14
+ end
15
+
16
+ def env_test_files
17
+ return unless ENV["TEST_FILES"] && !ENV["TEST_FILES"].empty?
18
+
19
+ @env_test_files ||= ENV["TEST_FILES"].split(",").map(&:strip)
20
+ end
21
+
22
+ def sqlserver_cases
23
+ @sqlserver_cases ||= Dir.glob("test/cases/*_test_sqlserver.rb")
24
+ end
25
+
26
+ def ar_cases
27
+ @ar_cases ||= begin
28
+ Dir.glob("#{ARTest::SQLServer.root_activerecord}/test/cases/**/*_test.rb").reject {
29
+ |x| x.include?("/adapters/") || x.include?("/encryption/performance")
30
+ }.sort
31
+ end
32
+ end
33
+
34
+ def test_files
35
+ if env_ar_test_files
36
+ [SQLSERVER_HELPER] + env_ar_test_files
37
+ elsif env_test_files
38
+ env_test_files
39
+ elsif ENV["ONLY_SQLSERVER"]
40
+ sqlserver_cases
41
+ elsif ENV["ONLY_ACTIVERECORD"]
42
+ [SQLSERVER_HELPER] + (ar_cases + [SQLSERVER_COERCED])
43
+ else
44
+ [SQLSERVER_HELPER] + (ar_cases + [SQLSERVER_COERCED] + sqlserver_cases)
45
+ end
46
+ end