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.
- checksums.yaml +7 -0
- data/.github/issue_template.md +22 -0
- data/.github/workflows/ci.yml +32 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Dockerfile.ci +14 -0
- data/Gemfile +26 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/RUNNING_UNIT_TESTS.md +38 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/activerecord-sqlserver-adapter-odbc-extended.gemspec +34 -0
- data/compose.ci.yaml +15 -0
- data/lib/active_record/connection_adapters/extended_sqlserver_adapter.rb +204 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/odbc_database_statements.rb +234 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary_ext.rb +25 -0
- data/lib/activerecord-sqlserver-adapter-odbc-extended.rb +12 -0
- data/test/appveyor/dbsetup.ps1 +27 -0
- data/test/appveyor/dbsetup.sql +11 -0
- data/test/cases/active_schema_test_sqlserver.rb +127 -0
- data/test/cases/adapter_test_sqlserver.rb +648 -0
- data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
- data/test/cases/change_column_null_test_sqlserver.rb +44 -0
- data/test/cases/coerced_tests.rb +2796 -0
- data/test/cases/column_test_sqlserver.rb +848 -0
- data/test/cases/connection_test_sqlserver.rb +138 -0
- data/test/cases/dbconsole.rb +19 -0
- data/test/cases/disconnected_test_sqlserver.rb +42 -0
- data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
- data/test/cases/enum_test_sqlserver.rb +49 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +57 -0
- data/test/cases/fetch_test_sqlserver.rb +88 -0
- data/test/cases/fully_qualified_identifier_test_sqlserver.rb +72 -0
- data/test/cases/helper_sqlserver.rb +61 -0
- data/test/cases/migration_test_sqlserver.rb +144 -0
- data/test/cases/order_test_sqlserver.rb +153 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +102 -0
- data/test/cases/primary_keys_test_sqlserver.rb +103 -0
- data/test/cases/rake_test_sqlserver.rb +198 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +296 -0
- data/test/cases/schema_test_sqlserver.rb +111 -0
- data/test/cases/trigger_test_sqlserver.rb +51 -0
- data/test/cases/utils_test_sqlserver.rb +129 -0
- data/test/cases/uuid_test_sqlserver.rb +54 -0
- data/test/cases/view_test_sqlserver.rb +58 -0
- data/test/config.yml +38 -0
- data/test/debug.rb +16 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
- data/test/migrations/create_clients_and_change_column_null.rb +25 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/alien.rb +5 -0
- data/test/models/sqlserver/booking.rb +5 -0
- data/test/models/sqlserver/composite_pk.rb +9 -0
- data/test/models/sqlserver/customers_view.rb +5 -0
- data/test/models/sqlserver/datatype.rb +5 -0
- data/test/models/sqlserver/datatype_migration.rb +10 -0
- data/test/models/sqlserver/dollar_table_name.rb +5 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +5 -0
- data/test/models/sqlserver/fk_has_pk.rb +5 -0
- data/test/models/sqlserver/natural_pk_data.rb +6 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +5 -0
- data/test/models/sqlserver/no_pk_data.rb +5 -0
- data/test/models/sqlserver/object_default.rb +5 -0
- data/test/models/sqlserver/quoted_table.rb +9 -0
- data/test/models/sqlserver/quoted_view_1.rb +5 -0
- data/test/models/sqlserver/quoted_view_2.rb +5 -0
- data/test/models/sqlserver/sst_memory.rb +5 -0
- data/test/models/sqlserver/sst_string_collation.rb +3 -0
- data/test/models/sqlserver/string_default.rb +5 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +5 -0
- data/test/models/sqlserver/string_defaults_view.rb +5 -0
- data/test/models/sqlserver/table_with_spaces.rb +5 -0
- data/test/models/sqlserver/tinyint_pk.rb +5 -0
- data/test/models/sqlserver/trigger.rb +17 -0
- data/test/models/sqlserver/trigger_history.rb +5 -0
- data/test/models/sqlserver/upper.rb +5 -0
- data/test/models/sqlserver/uppered.rb +5 -0
- data/test/models/sqlserver/uuid.rb +5 -0
- data/test/schema/datatypes/2012.sql +56 -0
- data/test/schema/enable-in-memory-oltp.sql +81 -0
- data/test/schema/sqlserver_specific_schema.rb +363 -0
- data/test/support/coerceable_test_sqlserver.rb +55 -0
- data/test/support/connection_reflection.rb +32 -0
- data/test/support/core_ext/query_cache.rb +38 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
- data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
- data/test/support/minitest_sqlserver.rb +3 -0
- data/test/support/paths_sqlserver.rb +50 -0
- data/test/support/query_assertions.rb +49 -0
- data/test/support/rake_helpers.rb +46 -0
- data/test/support/table_definition_sqlserver.rb +24 -0
- data/test/support/test_in_memory_oltp.rb +17 -0
- metadata +240 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
require "stringio"
|
|
5
|
+
|
|
6
|
+
class SchemaDumperTestSQLServer < ActiveRecord::TestCase
|
|
7
|
+
before { all_tables }
|
|
8
|
+
|
|
9
|
+
let(:all_tables) { ActiveRecord::Base.lease_connection.tables }
|
|
10
|
+
let(:schema) { @generated_schema }
|
|
11
|
+
|
|
12
|
+
it "sst_datatypes" do
|
|
13
|
+
generate_schema_for_table "sst_datatypes"
|
|
14
|
+
|
|
15
|
+
assert_line :bigint, type: "bigint", default: 42
|
|
16
|
+
assert_line :int, type: "integer", default: 42
|
|
17
|
+
assert_line :smallint, type: "integer", limit: 2, default: 42
|
|
18
|
+
assert_line :tinyint, type: "integer", limit: 1, default: 42
|
|
19
|
+
assert_line :bit, type: "boolean", default: true
|
|
20
|
+
assert_line :decimal_9_2, type: "decimal", precision: 9, scale: 2, default: 12345.01
|
|
21
|
+
assert_line :numeric_18_0, type: "decimal", precision: 18, default: 191
|
|
22
|
+
assert_line :numeric_36_2, type: "decimal", precision: 36, scale: 2, default: 12345678901234567890.01
|
|
23
|
+
assert_line :money, type: "money", precision: 19, scale: 4, default: 4.2
|
|
24
|
+
assert_line :smallmoney, type: "smallmoney", precision: 10, scale: 4, default: 4.2
|
|
25
|
+
# Approximate Numerics
|
|
26
|
+
assert_line :float, type: "float", default: 123.00000001
|
|
27
|
+
assert_line :real, type: "real", default: 123.45
|
|
28
|
+
# Date and Time
|
|
29
|
+
assert_line :date, type: "date", limit: nil, precision: nil, scale: nil, default: "01-01-1900"
|
|
30
|
+
assert_line :datetime, type: "datetime", limit: nil, precision: nil, scale: nil, default: "01-01-1753 00:00:00.123"
|
|
31
|
+
if connection_dblib_73?
|
|
32
|
+
assert_line :datetime2_7, type: "datetime", precision: 7, default: "12-31-9999 23:59:59.9999999"
|
|
33
|
+
assert_line :datetime2_3, type: "datetime", precision: 3
|
|
34
|
+
assert_line :datetime2_1, type: "datetime", precision: 1
|
|
35
|
+
end
|
|
36
|
+
assert_line :smalldatetime, type: "smalldatetime", default: "01-01-1901 15:45:00.0"
|
|
37
|
+
if connection_tds_73
|
|
38
|
+
assert_line :time_7, type: "time", precision: 7, default: "04:20:00.2883215"
|
|
39
|
+
assert_line :time_2, type: "time", precision: 2
|
|
40
|
+
assert_line :time_default, type: "time", precision: 7, default: "15:03:42.0621978"
|
|
41
|
+
end
|
|
42
|
+
# Character Strings
|
|
43
|
+
assert_line :char_10, type: "char", limit: 10, default: "1234567890"
|
|
44
|
+
assert_line :varchar_50, type: "varchar", limit: 50, default: "test varchar_50"
|
|
45
|
+
assert_line :varchar_max, type: "varchar_max", default: "test varchar_max"
|
|
46
|
+
assert_line :text, type: "text_basic", default: "test text"
|
|
47
|
+
# Unicode Character Strings
|
|
48
|
+
assert_line :nchar_10, type: "nchar", limit: 10, default: "12345678åå"
|
|
49
|
+
assert_line :nvarchar_50, type: "string", limit: 50, default: "test nvarchar_50 åå"
|
|
50
|
+
assert_line :nvarchar_max, type: "text", default: "test nvarchar_max åå"
|
|
51
|
+
assert_line :ntext, type: "ntext", default: "test ntext åå"
|
|
52
|
+
# Binary Strings
|
|
53
|
+
assert_line :binary_49, type: "binary_basic", limit: 49
|
|
54
|
+
assert_line :varbinary_49, type: "varbinary", limit: 49
|
|
55
|
+
assert_line :varbinary_max, type: "binary"
|
|
56
|
+
# Other Data Types
|
|
57
|
+
assert_line :uniqueidentifier, type: "uuid", default: -> { "newid()" }
|
|
58
|
+
assert_line :timestamp, type: "ss_timestamp"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "sst_datatypes_migration" do
|
|
62
|
+
columns = SSTestDatatypeMigration.columns_hash
|
|
63
|
+
generate_schema_for_table "sst_datatypes_migration"
|
|
64
|
+
|
|
65
|
+
# Simple Rails conventions
|
|
66
|
+
_(columns["integer_col"].sql_type).must_equal "int(4)"
|
|
67
|
+
_(columns["bigint_col"].sql_type).must_equal "bigint(8)"
|
|
68
|
+
_(columns["boolean_col"].sql_type).must_equal "bit"
|
|
69
|
+
_(columns["decimal_col"].sql_type).must_equal "decimal(18,0)"
|
|
70
|
+
_(columns["float_col"].sql_type).must_equal "float"
|
|
71
|
+
_(columns["string_col"].sql_type).must_equal "nvarchar(4000)"
|
|
72
|
+
_(columns["text_col"].sql_type).must_equal "nvarchar(max)"
|
|
73
|
+
_(columns["datetime_nil_precision_col"].sql_type).must_equal "datetime"
|
|
74
|
+
_(columns["datetime_col"].sql_type).must_equal "datetime2(6)"
|
|
75
|
+
_(columns["timestamp_col"].sql_type).must_equal "datetime2(6)"
|
|
76
|
+
_(columns["time_col"].sql_type).must_equal "time(7)"
|
|
77
|
+
_(columns["date_col"].sql_type).must_equal "date"
|
|
78
|
+
_(columns["binary_col"].sql_type).must_equal "varbinary(max)"
|
|
79
|
+
|
|
80
|
+
assert_line :integer_col, type: "integer"
|
|
81
|
+
assert_line :bigint_col, type: "bigint"
|
|
82
|
+
assert_line :boolean_col, type: "boolean"
|
|
83
|
+
assert_line :decimal_col, type: "decimal", precision: 18
|
|
84
|
+
assert_line :float_col, type: "float"
|
|
85
|
+
assert_line :string_col, type: "string"
|
|
86
|
+
assert_line :text_col, type: "text"
|
|
87
|
+
assert_line :datetime_nil_precision_col, type: "datetime", precision: nil
|
|
88
|
+
assert_line :datetime_col, type: "datetime"
|
|
89
|
+
assert_line :datetime_col, type: "datetime"
|
|
90
|
+
assert_line :timestamp_col, type: "datetime"
|
|
91
|
+
assert_line :time_col, type: "time", precision: 7
|
|
92
|
+
assert_line :date_col, type: "date"
|
|
93
|
+
assert_line :binary_col, type: "binary"
|
|
94
|
+
|
|
95
|
+
# Our type methods.
|
|
96
|
+
_(columns["real_col"].sql_type).must_equal "real"
|
|
97
|
+
_(columns["money_col"].sql_type).must_equal "money"
|
|
98
|
+
_(columns["smalldatetime_col"].sql_type).must_equal "smalldatetime"
|
|
99
|
+
_(columns["datetime2_col"].sql_type).must_equal "datetime2(7)"
|
|
100
|
+
_(columns["datetimeoffset"].sql_type).must_equal "datetimeoffset(7)"
|
|
101
|
+
_(columns["smallmoney_col"].sql_type).must_equal "smallmoney"
|
|
102
|
+
_(columns["char_col"].sql_type).must_equal "char(1)"
|
|
103
|
+
_(columns["varchar_col"].sql_type).must_equal "varchar(8000)"
|
|
104
|
+
_(columns["text_basic_col"].sql_type).must_equal "text"
|
|
105
|
+
_(columns["nchar_col"].sql_type).must_equal "nchar(1)"
|
|
106
|
+
_(columns["ntext_col"].sql_type).must_equal "ntext"
|
|
107
|
+
_(columns["binary_basic_col"].sql_type).must_equal "binary(1)"
|
|
108
|
+
_(columns["binary_basic_16_col"].sql_type).must_equal "binary(16)"
|
|
109
|
+
_(columns["varbinary_col"].sql_type).must_equal "varbinary(8000)"
|
|
110
|
+
_(columns["uuid_col"].sql_type).must_equal "uniqueidentifier"
|
|
111
|
+
_(columns["sstimestamp_col"].sql_type).must_equal "timestamp"
|
|
112
|
+
_(columns["json_col"].sql_type).must_equal "nvarchar(max)"
|
|
113
|
+
|
|
114
|
+
assert_line :real_col, type: "real"
|
|
115
|
+
assert_line :money_col, type: "money", precision: 19, scale: 4
|
|
116
|
+
assert_line :smalldatetime_col, type: "smalldatetime"
|
|
117
|
+
assert_line :datetime2_col, type: "datetime", precision: 7
|
|
118
|
+
assert_line :datetimeoffset, type: "datetimeoffset", precision: 7
|
|
119
|
+
assert_line :smallmoney_col, type: "smallmoney", precision: 10, scale: 4
|
|
120
|
+
assert_line :char_col, type: "char", limit: 1
|
|
121
|
+
assert_line :varchar_col, type: "varchar"
|
|
122
|
+
assert_line :text_basic_col, type: "text_basic"
|
|
123
|
+
assert_line :nchar_col, type: "nchar", limit: 1
|
|
124
|
+
assert_line :ntext_col, type: "ntext"
|
|
125
|
+
assert_line :binary_basic_col, type: "binary_basic", limit: 1
|
|
126
|
+
assert_line :binary_basic_16_col, type: "binary_basic", limit: 16
|
|
127
|
+
assert_line :varbinary_col, type: "varbinary"
|
|
128
|
+
assert_line :uuid_col, type: "uuid"
|
|
129
|
+
assert_line :sstimestamp_col, type: "ss_timestamp", null: false
|
|
130
|
+
assert_line :json_col, type: "text"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "dump column collation" do
|
|
134
|
+
generate_schema_for_table('sst_string_collation')
|
|
135
|
+
|
|
136
|
+
assert_line :string_without_collation, type: "string"
|
|
137
|
+
assert_line :string_default_collation, type: "varchar"
|
|
138
|
+
assert_line :string_with_collation, type: "varchar", collation: "SQL_Latin1_General_CP1_CS_AS"
|
|
139
|
+
assert_line :varchar_with_collation, type: "varchar", collation: "SQL_Latin1_General_CP1_CS_AS"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Special Cases
|
|
143
|
+
|
|
144
|
+
it "honor nonstandard primary keys" do
|
|
145
|
+
generate_schema_for_table("movies") do |output|
|
|
146
|
+
match = output.match(%r{create_table "movies"(.*)do})
|
|
147
|
+
assert_not_nil(match, "non-standard primary key table not found")
|
|
148
|
+
assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it "no id with model driven primary key" do
|
|
153
|
+
output = generate_schema_for_table "sst_no_pk_data"
|
|
154
|
+
|
|
155
|
+
_(output).must_match %r{create_table "sst_no_pk_data".*id:\sfalse.*do}
|
|
156
|
+
assert_line :name, type: "string"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "dumps field with unique key constraints only once" do
|
|
160
|
+
output = generate_schema_for_table "unique_key_dumped_table"
|
|
161
|
+
|
|
162
|
+
_(output.scan('t.integer "unique_field"').length).must_equal(1)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "schemas are dumped and tables names only include non-default schema" do
|
|
166
|
+
stream = StringIO.new
|
|
167
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
|
|
168
|
+
generated_schema = stream.string
|
|
169
|
+
|
|
170
|
+
# Only generate non-default schemas. Default schema is 'dbo'.
|
|
171
|
+
assert_not_includes generated_schema, 'create_schema "dbo"'
|
|
172
|
+
assert_not_includes generated_schema, 'create_schema "db_owner"'
|
|
173
|
+
assert_not_includes generated_schema, 'create_schema "INFORMATION_SCHEMA"'
|
|
174
|
+
assert_not_includes generated_schema, 'create_schema "sys"'
|
|
175
|
+
assert_not_includes generated_schema, 'create_schema "guest"'
|
|
176
|
+
assert_includes generated_schema, 'create_schema "test"'
|
|
177
|
+
assert_includes generated_schema, 'create_schema "test2"'
|
|
178
|
+
|
|
179
|
+
# Only non-default schemas should be included in table names. Default schema is 'dbo'.
|
|
180
|
+
assert_includes generated_schema, 'create_table "accounts"'
|
|
181
|
+
assert_includes generated_schema, 'create_table "test.aliens"'
|
|
182
|
+
assert_includes generated_schema, 'create_table "test2.sst_schema_test_multiple_schema"'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def generate_schema_for_table(*table_names)
|
|
188
|
+
previous_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
|
|
189
|
+
ActiveRecord::SchemaDumper.ignore_tables = all_tables - table_names
|
|
190
|
+
|
|
191
|
+
stream = StringIO.new
|
|
192
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, stream)
|
|
193
|
+
|
|
194
|
+
@generated_schema = stream.string
|
|
195
|
+
yield @generated_schema if block_given?
|
|
196
|
+
@schema_lines = Hash.new
|
|
197
|
+
type_matcher = /\A\s+t\.\w+\s+"(.*?)"[,\n]/
|
|
198
|
+
@generated_schema.each_line do |line|
|
|
199
|
+
next unless line =~ type_matcher
|
|
200
|
+
|
|
201
|
+
@schema_lines[Regexp.last_match[1]] = SchemaLine.new(line)
|
|
202
|
+
end
|
|
203
|
+
@generated_schema
|
|
204
|
+
ensure
|
|
205
|
+
ActiveRecord::SchemaDumper.ignore_tables = previous_ignore_tables
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def line(column_name)
|
|
209
|
+
@schema_lines[column_name.to_s]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def assert_line(column_name, expected_options = {})
|
|
213
|
+
line = line(column_name)
|
|
214
|
+
assert line, "Could not find line with column name: #{column_name.inspect} in schema:\n#{schema}"
|
|
215
|
+
|
|
216
|
+
# Check that the expected and actual option keys.
|
|
217
|
+
expected_options_keys = expected_options.keys
|
|
218
|
+
expected_options_keys.delete(:type)
|
|
219
|
+
_(expected_options_keys.sort).must_equal (line.options.keys.sort), "For column '#{column_name}' expected schema options and actual schema options do not match."
|
|
220
|
+
|
|
221
|
+
# Check the expected and actual option values.
|
|
222
|
+
expected_options.each do |key, expected|
|
|
223
|
+
actual = key == :type ? line.send(:type_method) : line.send(key)
|
|
224
|
+
|
|
225
|
+
message = "#{key.to_s.titleize} of #{expected.inspect} not found in:\n#{line}"
|
|
226
|
+
|
|
227
|
+
if expected.nil?
|
|
228
|
+
_(actual).must_be_nil message
|
|
229
|
+
elsif expected.is_a?(Array)
|
|
230
|
+
_(actual).must_include expected, message
|
|
231
|
+
elsif expected.is_a?(Float)
|
|
232
|
+
_(actual).must_be_close_to expected, 0.001
|
|
233
|
+
elsif expected.is_a?(Proc)
|
|
234
|
+
_(actual.call).must_equal(expected.call)
|
|
235
|
+
else
|
|
236
|
+
_(actual).must_equal expected, message
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class SchemaLine
|
|
242
|
+
LINE_PARSER = %r{t\.(\w+)\s+"(.*?)"[,\s+](.*)}
|
|
243
|
+
|
|
244
|
+
attr_reader :line,
|
|
245
|
+
:type_method,
|
|
246
|
+
:col_name,
|
|
247
|
+
:options
|
|
248
|
+
|
|
249
|
+
def self.option(method_name)
|
|
250
|
+
define_method(method_name) do
|
|
251
|
+
if options.key?(method_name.to_sym)
|
|
252
|
+
options[method_name.to_sym]
|
|
253
|
+
else
|
|
254
|
+
throw "Schema line does include the '#{method_name}' option!"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def initialize(line)
|
|
260
|
+
@line = line
|
|
261
|
+
@type_method, @col_name, @options = parse_line
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
option :limit
|
|
265
|
+
option :precision
|
|
266
|
+
option :scale
|
|
267
|
+
option :default
|
|
268
|
+
option :collation
|
|
269
|
+
option :null
|
|
270
|
+
|
|
271
|
+
def to_s
|
|
272
|
+
line.squish
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def inspect
|
|
276
|
+
"#<SchemaLine col_name=#{col_name.inspect}, options=#{options.inspect}>"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
|
|
281
|
+
def parse_line
|
|
282
|
+
_all, type_method, col_name, options = @line.match(LINE_PARSER).to_a
|
|
283
|
+
options = parse_options(options)
|
|
284
|
+
|
|
285
|
+
[type_method, col_name, options]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def parse_options(opts)
|
|
289
|
+
if opts.present?
|
|
290
|
+
eval "{#{opts}}"
|
|
291
|
+
else
|
|
292
|
+
{}
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class SchemaTestSQLServer < ActiveRecord::TestCase
|
|
6
|
+
describe "When table is dbo schema" do
|
|
7
|
+
it "find primary key for tables with odd schema" do
|
|
8
|
+
_(connection.primary_key("sst_natural_pk_data")).must_equal "legacy_id"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "When table is in non-dbo schema" do
|
|
13
|
+
it "work with table exists" do
|
|
14
|
+
assert connection.data_source_exists?("test.sst_schema_natural_id")
|
|
15
|
+
assert connection.data_source_exists?("[test].[sst_schema_natural_id]")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "find primary key for tables with odd schema" do
|
|
19
|
+
_(connection.primary_key("test.sst_schema_natural_id")).must_equal "legacy_id"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "have only one identity column" do
|
|
23
|
+
columns = connection.columns("test.sst_schema_identity")
|
|
24
|
+
|
|
25
|
+
assert_equal 2, columns.size
|
|
26
|
+
assert_equal 1, columns.select { |c| c.is_identity? }.size
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "read only column properties for table in specific schema" do
|
|
30
|
+
test_columns = connection.columns("test.sst_schema_columns")
|
|
31
|
+
dbo_columns = connection.columns("dbo.sst_schema_columns")
|
|
32
|
+
columns = connection.columns("sst_schema_columns") # This returns table from dbo schema
|
|
33
|
+
|
|
34
|
+
assert_equal 7, test_columns.size
|
|
35
|
+
assert_equal 2, dbo_columns.size
|
|
36
|
+
assert_equal 2, columns.size
|
|
37
|
+
assert_equal 1, test_columns.select { |c| c.is_identity? }.size
|
|
38
|
+
assert_equal 1, dbo_columns.select { |c| c.is_identity? }.size
|
|
39
|
+
assert_equal 1, columns.select { |c| c.is_identity? }.size
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "return correct varchar and nvarchar column limit length when table is in non-dbo schema" do
|
|
43
|
+
columns = connection.columns("test.sst_schema_columns")
|
|
44
|
+
|
|
45
|
+
assert_equal 255, columns.find { |c| c.name == "name" }.limit
|
|
46
|
+
assert_equal 1000, columns.find { |c| c.name == "description" }.limit
|
|
47
|
+
assert_equal 255, columns.find { |c| c.name == "n_name" }.limit
|
|
48
|
+
assert_equal 1000, columns.find { |c| c.name == "n_description" }.limit
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "parsing table name from raw SQL" do
|
|
53
|
+
describe 'SELECT statements' do
|
|
54
|
+
it do
|
|
55
|
+
assert_equal "[sst_schema_columns]", connection.send(:get_raw_table_name, "SELECT [sst_schema_columns].[id] FROM [sst_schema_columns]")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it do
|
|
59
|
+
assert_equal "sst_schema_columns", connection.send(:get_raw_table_name, "SELECT [sst_schema_columns].[id] FROM sst_schema_columns")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it do
|
|
63
|
+
assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES]")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it do
|
|
67
|
+
assert_equal "[WITH - SPACES$DOLLAR]", connection.send(:get_raw_table_name, "SELECT id FROM [WITH - SPACES$DOLLAR]")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it do
|
|
71
|
+
assert_nil connection.send(:get_raw_table_name, nil)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe 'INSERT statements' do
|
|
76
|
+
it do
|
|
77
|
+
assert_equal "[dashboards]", connection.send(:get_raw_table_name, "INSERT INTO [dashboards] DEFAULT VALUES; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it do
|
|
81
|
+
assert_equal "lock_without_defaults", connection.send(:get_raw_table_name, "INSERT INTO lock_without_defaults(title) VALUES('title1')")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it do
|
|
85
|
+
assert_equal "json_data_type", connection.send(:get_raw_table_name, "insert into json_data_type (payload) VALUES ('null')")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it do
|
|
89
|
+
assert_equal "[auto_increments]", connection.send(:get_raw_table_name, "INSERT INTO [auto_increments] OUTPUT INSERTED.[id] DEFAULT VALUES")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it do
|
|
93
|
+
assert_equal "[WITH - SPACES]", connection.send(:get_raw_table_name, "EXEC sp_executesql N'INSERT INTO [WITH - SPACES] ([external_id]) OUTPUT INSERTED.[id] VALUES (@0)', N'@0 bigint', @0 = 10")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it do
|
|
97
|
+
assert_equal "[test].[aliens]", connection.send(:get_raw_table_name, "EXEC sp_executesql N'INSERT INTO [test].[aliens] ([name]) OUTPUT INSERTED.[id] VALUES (@0)', N'@0 varchar(255)', @0 = 'Trisolarans'")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it do
|
|
101
|
+
assert_equal "[with].[select notation]", connection.send(:get_raw_table_name, "INSERT INTO [with].[select notation] SELECT * FROM [table_name]")
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe 'CREATE VIEW statements' do
|
|
106
|
+
it do
|
|
107
|
+
assert_equal "test_table_as", connection.send(:get_raw_table_name, "CREATE VIEW test_views ( test_table_a_id, test_table_b_id ) AS SELECT test_table_as.id as test_table_a_id, test_table_bs.id as test_table_b_id FROM (test_table_as with(nolock) LEFT JOIN test_table_bs with(nolock) ON (test_table_as.id = test_table_bs.test_table_a_id))")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class SQLServerTriggerTest < ActiveRecord::TestCase
|
|
6
|
+
after { exclude_output_inserted_table_names.clear }
|
|
7
|
+
|
|
8
|
+
let(:exclude_output_inserted_table_names) do
|
|
9
|
+
ActiveRecord::ConnectionAdapters::SQLServerAdapter.exclude_output_inserted_table_names
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "can insert into a table with output inserted - with a true setting for table name" do
|
|
13
|
+
exclude_output_inserted_table_names["sst_table_with_trigger"] = true
|
|
14
|
+
assert SSTestTriggerHistory.all.empty?
|
|
15
|
+
obj = SSTestTrigger.create! event_name: "test trigger"
|
|
16
|
+
_(["Fixnum", "Integer"]).must_include obj.id.class.name
|
|
17
|
+
_(obj.event_name).must_equal "test trigger"
|
|
18
|
+
_(obj.id).must_be :present?
|
|
19
|
+
_(obj.id.to_s).must_equal SSTestTriggerHistory.first.id_source
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "can insert into a table with output inserted - with a uniqueidentifier value" do
|
|
23
|
+
exclude_output_inserted_table_names["sst_table_with_uuid_trigger"] = "uniqueidentifier"
|
|
24
|
+
assert SSTestTriggerHistory.all.empty?
|
|
25
|
+
obj = SSTestTriggerUuid.create! event_name: "test uuid trigger"
|
|
26
|
+
_(obj.id.class.name).must_equal "String"
|
|
27
|
+
_(obj.event_name).must_equal "test uuid trigger"
|
|
28
|
+
_(obj.id).must_be :present?
|
|
29
|
+
_(obj.id.to_s).must_equal SSTestTriggerHistory.first.id_source
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "can insert into a table with composite pk with output inserted - with a true setting for table name" do
|
|
33
|
+
exclude_output_inserted_table_names["sst_table_with_composite_pk_trigger"] = true
|
|
34
|
+
assert SSTestTriggerHistory.all.empty?
|
|
35
|
+
obj = SSTestTriggerCompositePk.create! pk_col_one: 123, pk_col_two: 42, event_name: "test trigger"
|
|
36
|
+
_(obj.event_name).must_equal "test trigger"
|
|
37
|
+
_(obj.pk_col_one).must_equal 123
|
|
38
|
+
_(obj.pk_col_two).must_equal 42
|
|
39
|
+
_(obj.pk_col_one.to_s).must_equal SSTestTriggerHistory.first.id_source
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "can insert into a table with composite pk with different data type with output inserted - with a hash setting for table name" do
|
|
43
|
+
exclude_output_inserted_table_names["sst_table_with_composite_pk_trigger_with_different_data_type"] = { pk_col_one: "uniqueidentifier", pk_col_two: "int" }
|
|
44
|
+
assert SSTestTriggerHistory.all.empty?
|
|
45
|
+
obj = SSTestTriggerCompositePkWithDefferentDataType.create! pk_col_two: 123, event_name: "test trigger"
|
|
46
|
+
_(obj.event_name).must_equal "test trigger"
|
|
47
|
+
_(obj.pk_col_one).must_be :present?
|
|
48
|
+
_(obj.pk_col_two).must_equal 123
|
|
49
|
+
_(obj.pk_col_one.to_s).must_equal SSTestTriggerHistory.first.id_source
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class UtilsTestSQLServer < ActiveRecord::TestCase
|
|
6
|
+
it ".quote_string" do
|
|
7
|
+
_(SQLServer::Utils.quote_string("I'll store this in C:\\Users")).must_equal "I''ll store this in C:\\Users"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it ".unquote_string" do
|
|
11
|
+
_(SQLServer::Utils.unquote_string("I''ll store this in C:\\Users")).must_equal "I'll store this in C:\\Users"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it ".quoted_raw" do
|
|
15
|
+
_(SQLServer::Utils.quoted_raw("some.Name")).must_equal "[some.Name]"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe ".extract_identifiers constructor and thus SQLServer::Utils::Name value object" do
|
|
19
|
+
let(:valid_names) { valid_names_unquoted + valid_names_quoted }
|
|
20
|
+
|
|
21
|
+
let(:valid_names_unquoted) {
|
|
22
|
+
[
|
|
23
|
+
"server.database.schema.object",
|
|
24
|
+
"server.database..object",
|
|
25
|
+
"server..schema.object",
|
|
26
|
+
"server...object",
|
|
27
|
+
"database.schema.object",
|
|
28
|
+
"database..object",
|
|
29
|
+
"schema.object",
|
|
30
|
+
"object"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let(:valid_names_quoted) {
|
|
35
|
+
[
|
|
36
|
+
"[server].[database].[schema].[object]",
|
|
37
|
+
"[server].[database]..[object]",
|
|
38
|
+
"[server]..[schema].[object]",
|
|
39
|
+
"[server]...[object]",
|
|
40
|
+
"[database].[schema].[object]",
|
|
41
|
+
"[database]..[object]",
|
|
42
|
+
"[schema].[object]",
|
|
43
|
+
"[object]"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let(:server_names) { valid_names.partition { |name| name =~ /server/ } }
|
|
48
|
+
let(:database_names) { valid_names.partition { |name| name =~ /database/ } }
|
|
49
|
+
let(:schema_names) { valid_names.partition { |name| name =~ /schema/ } }
|
|
50
|
+
|
|
51
|
+
it "extracts and returns #object identifier unquoted by default or quoted as needed" do
|
|
52
|
+
valid_names.each do |n|
|
|
53
|
+
name = extract_identifiers(n)
|
|
54
|
+
_(name.object).must_equal "object", "With #{n.inspect} for #object"
|
|
55
|
+
_(name.object_quoted).must_equal "[object]", "With #{n.inspect} for #object_quoted"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
[:schema, :database, :server].each do |part|
|
|
60
|
+
it "extracts and returns #{part} identifier unquoted by default or quoted as needed" do
|
|
61
|
+
present, blank = send(:"#{part}_names")
|
|
62
|
+
present.each do |n|
|
|
63
|
+
name = extract_identifiers(n)
|
|
64
|
+
_(name.send(:"#{part}")).must_equal "#{part}", "With #{n.inspect} for ##{part} method"
|
|
65
|
+
_(name.send(:"#{part}_quoted")).must_equal "[#{part}]", "With #{n.inspect} for ##{part}_quoted method"
|
|
66
|
+
end
|
|
67
|
+
blank.each do |n|
|
|
68
|
+
name = extract_identifiers(n)
|
|
69
|
+
_(name.send(:"#{part}")).must_be_nil "With #{n.inspect} for ##{part} method"
|
|
70
|
+
_(name.send(:"#{part}_quoted")).must_be_nil "With #{n.inspect} for ##{part}_quoted method"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "does not blow up on nil or blank string name" do
|
|
76
|
+
_(extract_identifiers(nil).object).must_be_nil
|
|
77
|
+
_(extract_identifiers(" ").object).must_be_nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "has a #quoted that returns a fully quoted name with all identifiers as originally passed in" do
|
|
81
|
+
_(extract_identifiers("object").quoted).must_equal "[object]"
|
|
82
|
+
_(extract_identifiers("server.database..object").quoted).must_equal "[server].[database]..[object]"
|
|
83
|
+
_(extract_identifiers("[server]...[object]").quoted).must_equal "[server]...[object]"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "can take a symbol argument" do
|
|
87
|
+
_(extract_identifiers(:object).object).must_equal "object"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "allows identifiers with periods to work" do
|
|
91
|
+
_(extract_identifiers("[obj.name]").quoted).must_equal "[obj.name]"
|
|
92
|
+
_(extract_identifiers("[obj.name].[foo]").quoted).must_equal "[obj.name].[foo]"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should indicate if a name is fully qualified" do
|
|
96
|
+
_(extract_identifiers("object").fully_qualified?).must_equal false
|
|
97
|
+
_(extract_identifiers("schema.object").fully_qualified?).must_equal false
|
|
98
|
+
_(extract_identifiers("database.schema.object").fully_qualified?).must_equal false
|
|
99
|
+
_(extract_identifiers("database.object").fully_qualified?).must_equal false
|
|
100
|
+
_(extract_identifiers("server...object").fully_qualified?).must_equal false
|
|
101
|
+
_(extract_identifiers("server.database..object").fully_qualified?).must_equal false
|
|
102
|
+
_(extract_identifiers("server.database.schema.object").fully_qualified?).must_equal true
|
|
103
|
+
_(extract_identifiers("server.database.schema.").fully_qualified?).must_equal true
|
|
104
|
+
_(extract_identifiers("[obj.name]").fully_qualified?).must_equal false
|
|
105
|
+
_(extract_identifiers("[schema].[obj.name]").fully_qualified?).must_equal false
|
|
106
|
+
_(extract_identifiers("[database].[schema].[obj.name]").fully_qualified?).must_equal false
|
|
107
|
+
_(extract_identifiers("[database].[obj.name]").fully_qualified?).must_equal false
|
|
108
|
+
_(extract_identifiers("[server.name]...[obj.name]").fully_qualified?).must_equal false
|
|
109
|
+
_(extract_identifiers("[server.name].[database]..[obj.name]").fully_qualified?).must_equal false
|
|
110
|
+
_(extract_identifiers("[server.name].[database].[schema].[obj.name]").fully_qualified?).must_equal true
|
|
111
|
+
_(extract_identifiers("[server.name].[database].[schema].").fully_qualified?).must_equal true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "can return fully qualified quoted table name" do
|
|
115
|
+
name = extract_identifiers("[my.server].db.schema.")
|
|
116
|
+
_(name.fully_qualified_database_quoted).must_equal "[my.server].[db]"
|
|
117
|
+
name = extract_identifiers("[server.name].[database].[schema].[object]")
|
|
118
|
+
_(name.fully_qualified_database_quoted).must_equal "[server.name].[database]"
|
|
119
|
+
name = extract_identifiers("server.database.schema.object")
|
|
120
|
+
_(name.fully_qualified_database_quoted).must_equal "[server].[database]"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def extract_identifiers(name)
|
|
127
|
+
SQLServer::Utils.extract_identifiers(name)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_sqlserver"
|
|
4
|
+
|
|
5
|
+
class SQLServerUuidTest < ActiveRecord::TestCase
|
|
6
|
+
let(:acceptable_uuid) { ActiveRecord::ConnectionAdapters::SQLServer::Type::Uuid::ACCEPTABLE_UUID }
|
|
7
|
+
|
|
8
|
+
it "has a uuid primary key" do
|
|
9
|
+
_(SSTestUuid.columns_hash["id"].type).must_equal :uuid
|
|
10
|
+
assert SSTestUuid.primary_key
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "can create with a new pk" do
|
|
14
|
+
obj = SSTestUuid.create!
|
|
15
|
+
_(obj.id).must_be :present?
|
|
16
|
+
_(obj.id).must_match acceptable_uuid
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "can create other uuid column on reload" do
|
|
20
|
+
obj = SSTestUuid.create!
|
|
21
|
+
obj.reload
|
|
22
|
+
_(obj.other_uuid).must_match acceptable_uuid
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "can find uuid pk via connection" do
|
|
26
|
+
_(connection.primary_key(SSTestUuid.table_name)).must_equal "id"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "changing column default" do
|
|
30
|
+
table_name = SSTestUuid.table_name
|
|
31
|
+
connection.add_column table_name, :thingy, :uuid, null: false, default: "NEWSEQUENTIALID()"
|
|
32
|
+
SSTestUuid.reset_column_information
|
|
33
|
+
column = SSTestUuid.columns_hash["thingy"]
|
|
34
|
+
_(column.default_function).must_equal "newsequentialid()"
|
|
35
|
+
# Now to a different function.
|
|
36
|
+
connection.change_column table_name, :thingy, :uuid, null: false, default: "NEWID()"
|
|
37
|
+
SSTestUuid.reset_column_information
|
|
38
|
+
column = SSTestUuid.columns_hash["thingy"]
|
|
39
|
+
_(column.default_function).must_equal "newid()"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "can insert even when use_output_inserted to false " do
|
|
43
|
+
obj = with_use_output_inserted_disabled { SSTestUuid.create!(name: "😢") }
|
|
44
|
+
_(obj.id).must_be :nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "can add column with proc as default" do
|
|
48
|
+
table_name = SSTestUuid.table_name
|
|
49
|
+
connection.add_column table_name, :thingy, :uuid, null: false, default: -> { "NEWSEQUENTIALID()" }
|
|
50
|
+
SSTestUuid.reset_column_information
|
|
51
|
+
column = SSTestUuid.columns_hash["thingy"]
|
|
52
|
+
_(column.default_function).must_equal "newsequentialid()"
|
|
53
|
+
end
|
|
54
|
+
end
|