mkxms-mssql 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -10
- data/lib/mkxms/mssql.rb +18 -0
- data/lib/mkxms/mssql/adoption_script_writer.rb +759 -91
- data/lib/mkxms/mssql/clr_aggregate_handler.rb +98 -0
- data/lib/mkxms/mssql/clr_assembly_handler.rb +92 -0
- data/lib/mkxms/mssql/clr_function_handler.rb +172 -0
- data/lib/mkxms/mssql/clr_impl.rb +58 -0
- data/lib/mkxms/mssql/clr_stored_procedure_handler.rb +88 -0
- data/lib/mkxms/mssql/clr_type_handler.rb +92 -0
- data/lib/mkxms/mssql/database_handler.rb +124 -3
- data/lib/mkxms/mssql/declaratives_creator.rb +206 -0
- data/lib/mkxms/mssql/dml_trigger_handler.rb +107 -0
- data/lib/mkxms/mssql/filegroup_handler.rb +1 -4
- data/lib/mkxms/mssql/function_handler.rb +1 -4
- data/lib/mkxms/mssql/indented_string_builder.rb +8 -2
- data/lib/mkxms/mssql/index_handler.rb +1 -4
- data/lib/mkxms/mssql/keywords.rb +492 -0
- data/lib/mkxms/mssql/primary_key_handler.rb +1 -4
- data/lib/mkxms/mssql/property_handler.rb +8 -0
- data/lib/mkxms/mssql/query_cursor.rb +12 -4
- data/lib/mkxms/mssql/references_handler.rb +24 -0
- data/lib/mkxms/mssql/role_handler.rb +1 -4
- data/lib/mkxms/mssql/scalar_type_handler.rb +108 -0
- data/lib/mkxms/mssql/schema_handler.rb +1 -4
- data/lib/mkxms/mssql/sql_string_manipulators.rb +4 -4
- data/lib/mkxms/mssql/statistics_handler.rb +1 -4
- data/lib/mkxms/mssql/stored_procedure_handler.rb +1 -4
- data/lib/mkxms/mssql/synonym_handler.rb +40 -0
- data/lib/mkxms/mssql/table_handler.rb +2 -8
- data/lib/mkxms/mssql/table_type_handler.rb +254 -0
- data/lib/mkxms/mssql/utils.rb +96 -0
- data/lib/mkxms/mssql/version.rb +1 -1
- data/lib/mkxms/mssql/view_handler.rb +1 -4
- data/spec/utils/indented_string_builder_spec.rb +21 -0
- data/spec/utils/query_cursor_spec.rb +2 -2
- data/spec/utils/sql_string_manipulators_spec.rb +59 -0
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acbe79b89bb652174600791fc2290a37d6bbd19a
|
4
|
+
data.tar.gz: a1d4a0b1871fd80f0bb28e7d8eeed9f1b36f626d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39a7e7fc9bfefa49985c3625699194e940741d0475d8279b8629af38cee368cd980069c78a0bdeed35b8d9275920374a751c63b643d8f1e87ce649581f69001f
|
7
|
+
data.tar.gz: bc8bc5753d47aacb298fb3d76887fe607095d3b75b5e6c602eee6add90d2e1879643d81108371a643a4a8b38022ca25b93f4609341f6302b8f1eb1d9b8c745f4
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Mkxms::Mssql
|
1
|
+
# Mkxms::Mssql [](http://badge.fury.io/rb/mkxms-mssql)
|
2
2
|
|
3
3
|
Creates a set of XMigra (https://rubygems.org/gems/xmigra) source files
|
4
4
|
from a database description XML document (as generated by
|
5
|
-
[mssql-eyewkas.sql](https://gist.github.com/rtweeks/62d8fb9c6ca3de1195d9
|
5
|
+
[mssql-eyewkas.sql](https://gist.github.com/rtweeks/62d8fb9c6ca3de1195d9/9d38f51f5f02a65e6146875c15714f9138a3b86c)
|
6
6
|
or a compatible later version).
|
7
7
|
|
8
8
|
## Installation
|
@@ -18,22 +18,16 @@ Run `mkxms-mssql [-o DEST_DIR] [DB_DESCRIPTION_XML_FILE]`
|
|
18
18
|
## Project Status
|
19
19
|
|
20
20
|
The 1.x series of releases is intended to incrementally include support for
|
21
|
-
additional Microsoft SQL Server features. As of version 1.
|
21
|
+
additional Microsoft SQL Server features. As of version 1.1, the following
|
22
22
|
features are NOT supported by this program (although they may be supported
|
23
23
|
by XMigra):
|
24
24
|
|
25
25
|
* Partition functions and partition schemes
|
26
|
-
* CLR assemblies
|
27
|
-
* Synonyms
|
28
26
|
* XML schema collections
|
29
|
-
* User-defined (SQL) types - scalar or table
|
30
|
-
* CLR types
|
31
27
|
* Fulltext indexes
|
32
28
|
* Rules
|
33
|
-
* Triggers
|
29
|
+
* DDL Triggers
|
34
30
|
* Spatial indexes
|
35
|
-
* CLR stored procedures
|
36
|
-
* CLR functions
|
37
31
|
* Service Broker configuration
|
38
32
|
|
39
33
|
Any elements in the database description XML relating to the features above
|
data/lib/mkxms/mssql.rb
CHANGED
@@ -16,6 +16,11 @@ module Mkxms
|
|
16
16
|
flags.on('-o', '--outdir=SCHEMA_DIR', "Output in SCHEMA_DIR") do |schema_dir|
|
17
17
|
options.schema_dir = Pathname(schema_dir).expand_path
|
18
18
|
end
|
19
|
+
|
20
|
+
options.generate_declaratives = true
|
21
|
+
flags.on('--[no-]declaratives', "Generate declarative support files") do |v|
|
22
|
+
options.generate_declaratives = v
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
21
26
|
db_files = optparser.parse(argv)
|
@@ -32,6 +37,16 @@ module Mkxms
|
|
32
37
|
engine = Engine.new(document, db_handler)
|
33
38
|
engine.run
|
34
39
|
db_handler.create_source_files
|
40
|
+
if generate_declaratives_indicated(options)
|
41
|
+
DeclarativesCreator.new(document, options[:schema_dir]).create_artifacts
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.generate_declaratives_indicated(options)
|
46
|
+
options[:generate_declaratives].tap do |val|
|
47
|
+
return val unless val.nil?
|
48
|
+
end
|
49
|
+
return Gem::Version.new(XMigra::VERSION) >= Gem::Version.new("1.6.0")
|
35
50
|
end
|
36
51
|
|
37
52
|
def self.with_db_description_io(file_path, &blk)
|
@@ -53,9 +68,12 @@ module Mkxms
|
|
53
68
|
end
|
54
69
|
|
55
70
|
require "mkxms/mssql/database_handler"
|
71
|
+
require "mkxms/mssql/declaratives_creator"
|
56
72
|
require "mkxms/mssql/engine"
|
57
73
|
require "mkxms/mssql/exceptions"
|
58
74
|
require "mkxms/mssql/version"
|
75
|
+
require "rubygems"
|
76
|
+
require "xmigra"
|
59
77
|
|
60
78
|
if __FILE__.eql? $0
|
61
79
|
Mkxms::Mssql.run_program
|
@@ -3,6 +3,7 @@ require 'xmigra'
|
|
3
3
|
require 'mkxms/mssql/indented_string_builder'
|
4
4
|
require 'mkxms/mssql/query_cursor'
|
5
5
|
require 'mkxms/mssql/sql_string_manipulators'
|
6
|
+
require 'mkxms/mssql/utils'
|
6
7
|
|
7
8
|
module Mkxms; end
|
8
9
|
|
@@ -20,13 +21,17 @@ module Mkxms::Mssql
|
|
20
21
|
attr_reader :db_expectations
|
21
22
|
|
22
23
|
def create_script(path)
|
24
|
+
if Utils.dry_run?
|
25
|
+
base, _, ext = path.to_s.rpartition('.')
|
26
|
+
path = [base, 'dry-run', ext].join('.')
|
27
|
+
end
|
23
28
|
Pathname(path).open('w') do |script|
|
24
29
|
script.puts adoption_sql
|
25
30
|
end
|
26
31
|
end
|
27
32
|
|
28
33
|
def adoption_sql
|
29
|
-
in_ddl_transaction do
|
34
|
+
in_ddl_transaction(dry_run: Utils.dry_run?) do
|
30
35
|
script_parts = [
|
31
36
|
# Check for blatantly incorrect application of script, e.g. running
|
32
37
|
# on master or template database.
|
@@ -39,6 +44,9 @@ module Mkxms::Mssql
|
|
39
44
|
# Create an error table
|
40
45
|
:create_adoption_error_table_sql,
|
41
46
|
|
47
|
+
# Check CLR assemblies
|
48
|
+
:check_clr_assemblies,
|
49
|
+
|
42
50
|
# Check roles
|
43
51
|
:check_expected_roles_exist_sql,
|
44
52
|
:check_expected_role_membership_sql,
|
@@ -46,6 +54,15 @@ module Mkxms::Mssql
|
|
46
54
|
# Check schemas
|
47
55
|
:check_expected_schemas_exist_sql,
|
48
56
|
|
57
|
+
# Check user-defined types
|
58
|
+
:check_user_defined_types_sql,
|
59
|
+
|
60
|
+
# Check CLR types
|
61
|
+
:check_clr_types,
|
62
|
+
|
63
|
+
# Check CLR aggregates
|
64
|
+
:check_clr_aggregates,
|
65
|
+
|
49
66
|
# Check tables (including columns)
|
50
67
|
:check_tables_exist_and_structured_as_expected_sql,
|
51
68
|
|
@@ -61,6 +78,9 @@ module Mkxms::Mssql
|
|
61
78
|
# Check check constraints
|
62
79
|
:check_check_constraints_sql,
|
63
80
|
|
81
|
+
# Check DML triggers
|
82
|
+
:check_dml_triggers,
|
83
|
+
|
64
84
|
# Adopt indexes
|
65
85
|
:adopt_indexes_sql,
|
66
86
|
|
@@ -109,7 +129,7 @@ module Mkxms::Mssql
|
|
109
129
|
)
|
110
130
|
BEGIN
|
111
131
|
DROP TABLE [xmigra].[adoption_errors];
|
112
|
-
END
|
132
|
+
END
|
113
133
|
GO
|
114
134
|
|
115
135
|
CREATE TABLE [xmigra].[adoption_errors] (
|
@@ -130,13 +150,55 @@ module Mkxms::Mssql
|
|
130
150
|
BEGIN
|
131
151
|
SELECT * FROM [xmigra].[adoption_errors];
|
132
152
|
RAISERROR (N'Database adoption failed.', 11, 1);
|
133
|
-
END
|
153
|
+
END
|
134
154
|
|
135
155
|
DROP TABLE [xmigra].[adoption_errors];
|
136
156
|
}
|
137
157
|
end
|
138
158
|
end
|
139
159
|
|
160
|
+
def check_clr_assemblies
|
161
|
+
db_expectations.clr_assemblies.map do |asm|
|
162
|
+
dedent %Q{
|
163
|
+
IF NOT EXISTS (
|
164
|
+
SELECT * FROM sys.assemblies asm
|
165
|
+
WHERE asm.is_visible = 1 AND QUOTENAME(asm.name) = #{asm.name.sql_quoted}
|
166
|
+
)
|
167
|
+
BEGIN
|
168
|
+
#{adoption_error_sql "CLR assembly #{asm.name} does not exist."}
|
169
|
+
END
|
170
|
+
|
171
|
+
IF NOT EXISTS (
|
172
|
+
SELECT * FROM sys.assemblies asm
|
173
|
+
JOIN sys.database_principals owner ON asm.principal_id = owner.principal_id
|
174
|
+
WHERE asm.is_visible = 1 AND QUOTENAME(asm.name) = #{asm.name.sql_quoted}
|
175
|
+
AND QUOTENAME(owner.name) = #{asm.owner.sql_quoted}
|
176
|
+
)
|
177
|
+
BEGIN
|
178
|
+
#{adoption_error_sql "CLR assembly #{asm.name} should be owned by #{asm.owner}"}
|
179
|
+
END
|
180
|
+
|
181
|
+
IF NOT EXISTS (
|
182
|
+
SELECT * FROM sys.assemblies asm
|
183
|
+
WHERE asm.is_visible = 1 AND QUOTENAME(asm.name) = #{asm.name.sql_quoted}
|
184
|
+
AND REPLACE(LOWER(asm.permission_set_desc), '_', '-') = #{asm.access.sql_quoted}
|
185
|
+
)
|
186
|
+
BEGIN
|
187
|
+
#{adoption_error_sql "CLR assembly #{asm.name} should have permission set #{asm.access}"}
|
188
|
+
END
|
189
|
+
|
190
|
+
IF NOT EXISTS (
|
191
|
+
SELECT * FROM sys.assemblies asm
|
192
|
+
WHERE asm.is_visible = 1 AND QUOTENAME(asm.name) = #{asm.name.sql_quoted}
|
193
|
+
AND asm.clr_name = #{asm.lib_name.sql_quoted}
|
194
|
+
)
|
195
|
+
BEGIN
|
196
|
+
#{adoption_error_sql %Q{CLR assembly #{asm.name} should reference library "#{asm.lib_name}".}}
|
197
|
+
END
|
198
|
+
}
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
140
202
|
def check_expected_roles_exist_sql
|
141
203
|
db_expectations.roles.map do |r|
|
142
204
|
dedent %Q{
|
@@ -147,7 +209,7 @@ module Mkxms::Mssql
|
|
147
209
|
)
|
148
210
|
BEGIN
|
149
211
|
#{adoption_error_sql "Role #{r.name} does not exist."}
|
150
|
-
END
|
212
|
+
END
|
151
213
|
|
152
214
|
IF EXISTS (
|
153
215
|
SELECT * FROM sys.database_principals r
|
@@ -157,7 +219,7 @@ module Mkxms::Mssql
|
|
157
219
|
)
|
158
220
|
BEGIN
|
159
221
|
#{adoption_error_sql "Role #{r.name} should be owned by #{r.owner}."}
|
160
|
-
END
|
222
|
+
END
|
161
223
|
}
|
162
224
|
end.join("\n")
|
163
225
|
end
|
@@ -176,7 +238,7 @@ module Mkxms::Mssql
|
|
176
238
|
)
|
177
239
|
BEGIN
|
178
240
|
#{adoption_error_sql "Role #{r.name} should be a member of #{er_name}."}
|
179
|
-
END
|
241
|
+
END
|
180
242
|
})
|
181
243
|
end
|
182
244
|
end
|
@@ -200,7 +262,326 @@ module Mkxms::Mssql
|
|
200
262
|
)
|
201
263
|
BEGIN
|
202
264
|
#{adoption_error_sql "Schema #{schema.name} is not owned by #{schema.owner}."}
|
203
|
-
END
|
265
|
+
END
|
266
|
+
}
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def check_user_defined_types_sql
|
271
|
+
db_expectations.types.map do |t|
|
272
|
+
dedent %Q{
|
273
|
+
IF NOT EXISTS (
|
274
|
+
SELECT * FROM sys.types t
|
275
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
276
|
+
WHERE QUOTENAME(s.name) = #{t.schema.sql_quoted}
|
277
|
+
AND QUOTENAME(t.name) = #{t.name.sql_quoted}
|
278
|
+
)
|
279
|
+
BEGIN
|
280
|
+
#{adoption_error_sql "User-defined scalar type #{t.qualified_name} is not defined."}
|
281
|
+
END
|
282
|
+
|
283
|
+
IF NOT EXISTS (
|
284
|
+
SELECT * FROM sys.types t
|
285
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
286
|
+
WHERE t.is_user_defined <> 0
|
287
|
+
AND t.is_assembly_type = 0
|
288
|
+
AND t.user_type_id NOT IN (
|
289
|
+
SELECT st.system_type_id
|
290
|
+
FROM sys.types st
|
291
|
+
)
|
292
|
+
AND QUOTENAME(s.name) = #{t.schema.sql_quoted}
|
293
|
+
AND QUOTENAME(t.name) = #{t.name.sql_quoted}
|
294
|
+
)
|
295
|
+
BEGIN
|
296
|
+
#{adoption_error_sql "#{t.qualified_name} is not a user-defined type."}
|
297
|
+
END
|
298
|
+
|
299
|
+
} + (
|
300
|
+
if t.respond_to?(:columns)
|
301
|
+
check_table_type_components_sql(t)
|
302
|
+
else
|
303
|
+
dedent %Q{
|
304
|
+
IF NOT EXISTS (
|
305
|
+
SELECT * FROM sys.types t
|
306
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
307
|
+
JOIN sys.types bt ON t.system_type_id = bt.user_type_id
|
308
|
+
WHERE t.is_user_defined <> 0
|
309
|
+
AND QUOTENAME(s.name) = #{t.schema.sql_quoted}
|
310
|
+
AND QUOTENAME(t.name) = #{t.name.sql_quoted}
|
311
|
+
AND QUOTENAME(bt.name) = #{t.base_type.sql_quoted}
|
312
|
+
AND t.is_nullable #{t.nullable? ? "<>" : "="} 0
|
313
|
+
#{
|
314
|
+
case t.capacity
|
315
|
+
when 'max'
|
316
|
+
"AND t.max_length = -1"
|
317
|
+
when Integer
|
318
|
+
"AND t.max_length = #{t.element_size * t.capacity}"
|
319
|
+
end
|
320
|
+
}
|
321
|
+
#{"AND t.precision = #{t.precision}" if t.precision}
|
322
|
+
#{"AND t.scale = #{t.scale}" if t.scale}
|
323
|
+
)
|
324
|
+
BEGIN
|
325
|
+
#{adoption_error_sql "#{t.qualified_name} is not defined as #{t.type_spec}."}
|
326
|
+
END
|
327
|
+
}
|
328
|
+
end
|
329
|
+
)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
class TableTypeKeyConstraintChecks < IndentedStringBuilder
|
334
|
+
include SqlStringManipulators
|
335
|
+
extend SqlStringManipulators
|
336
|
+
|
337
|
+
def initialize(table, constraint, error_sql_proc)
|
338
|
+
super()
|
339
|
+
|
340
|
+
@table = table
|
341
|
+
@constraint = constraint
|
342
|
+
@error_sql_proc = error_sql_proc
|
343
|
+
|
344
|
+
add_tests
|
345
|
+
end
|
346
|
+
|
347
|
+
attr_reader :table, :constraint
|
348
|
+
|
349
|
+
def error_sql(s)
|
350
|
+
@error_sql_proc.call(s)
|
351
|
+
end
|
352
|
+
|
353
|
+
def add_tests
|
354
|
+
dsl {
|
355
|
+
puts "IF NOT EXISTS(%s)" do
|
356
|
+
puts dedent %Q{
|
357
|
+
SELECT * FROM sys.key_constraints kc
|
358
|
+
JOIN sys.table_types tt ON tt.type_table_object_id = kc.parent_object_id
|
359
|
+
WHERE tt.user_type_id = TYPE_ID(#{table.qualified_name.sql_quoted})
|
360
|
+
AND kc.type = #{
|
361
|
+
case constraint.type
|
362
|
+
when 'PRIMARY KEY' then 'PK'
|
363
|
+
when 'UNIQUE' then 'UQ'
|
364
|
+
else raise "Unknown key constraint type"
|
365
|
+
end.sql_quoted
|
366
|
+
}
|
367
|
+
}
|
368
|
+
constraint.columns.each_with_index do |col, i|
|
369
|
+
puts dedent %Q{
|
370
|
+
AND (
|
371
|
+
SELECT ic.key_ordinal
|
372
|
+
FROM sys.index_columns ic
|
373
|
+
JOIN sys.columns c
|
374
|
+
ON c.object_id = ic.object_id
|
375
|
+
AND c.column_id = ic.column_id
|
376
|
+
WHERE ic.object_id = kc.parent_object_id
|
377
|
+
AND ic.index_id = kc.unique_index_id
|
378
|
+
AND QUOTENAME(c.name) = #{col.name.sql_quoted}
|
379
|
+
) = #{i + 1}
|
380
|
+
}
|
381
|
+
end
|
382
|
+
end
|
383
|
+
puts "BEGIN"..."END" do
|
384
|
+
puts error_sql "Table type #{table.qualified_name} does not have a #{constraint.type} constraint with the expected sequence of columns (#{constraint.columns.map(&:name).join(', ')})."
|
385
|
+
end
|
386
|
+
}
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def check_table_type_components_sql(t)
|
391
|
+
[].tap do |tests|
|
392
|
+
t.columns.each do |col|
|
393
|
+
# The column itself
|
394
|
+
tests << (dedent %Q{
|
395
|
+
IF NOT EXISTS (
|
396
|
+
SELECT * FROM sys.columns c
|
397
|
+
JOIN sys.table_types tt ON tt.type_table_object_id = c.object_id
|
398
|
+
JOIN sys.schemas ts ON ts.schema_id = tt.schema_id
|
399
|
+
JOIN sys.types ct ON ct.user_type_id = c.user_type_id
|
400
|
+
JOIN sys.schemas cts ON cts.schema_id = ct.schema_id
|
401
|
+
WHERE QUOTENAME(c.name) = #{col.name.sql_quoted}
|
402
|
+
AND QUOTENAME(cts.name) = #{(col.type_schema || "[sys]").sql_quoted}
|
403
|
+
AND QUOTENAME(ct.name) = #{col.type_name.sql_quoted}
|
404
|
+
)
|
405
|
+
BEGIN
|
406
|
+
#{adoption_error_sql "Table type #{t.qualified_name} does not have a column named #{col.name}."}
|
407
|
+
END
|
408
|
+
|
409
|
+
IF NOT EXISTS (
|
410
|
+
SELECT * FROM sys.columns c
|
411
|
+
JOIN sys.table_types tt ON tt.type_table_object_id = c.object_id
|
412
|
+
JOIN sys.schemas ts ON ts.schema_id = tt.schema_id
|
413
|
+
JOIN sys.types ct ON ct.user_type_id = c.user_type_id
|
414
|
+
JOIN sys.schemas cts ON cts.schema_id = ct.schema_id
|
415
|
+
WHERE QUOTENAME(c.name) = #{col.name.sql_quoted}
|
416
|
+
AND QUOTENAME(cts.name) = #{(col.type_schema || "[sys]").sql_quoted}
|
417
|
+
AND QUOTENAME(ct.name) = #{col.type_name.sql_quoted}
|
418
|
+
AND c.is_nullable = #{col.nullable? ? 1 : 0}
|
419
|
+
#{"AND c.max_length = #{col.max_byte_consumption}" if col.capacity}
|
420
|
+
#{"AND c.collation_name = #{col.collation.sql_quoted}" if col.collation}
|
421
|
+
#{"AND c.precision = #{col.precision}" if col.precision}
|
422
|
+
#{"AND c.scale = #{col.scale}" if col.scale}
|
423
|
+
)
|
424
|
+
BEGIN
|
425
|
+
#{adoption_error_sql "Column #{col.name} of #{t.qualified_name} is not defined as #{col.type_spec}."}
|
426
|
+
END
|
427
|
+
})
|
428
|
+
|
429
|
+
# Column constraints
|
430
|
+
col.check_constraints.each do |cstrt|
|
431
|
+
tests << (dedent %Q{
|
432
|
+
IF NOT EXISTS (
|
433
|
+
SELECT * FROM sys.check_constraints cc
|
434
|
+
JOIN sys.columns c
|
435
|
+
ON c.object_id = cc.parent_object_id
|
436
|
+
AND c.column_id = cc.parent_column_id
|
437
|
+
JOIN sys.table_types tt ON tt.type_table_object_id = c.object_id
|
438
|
+
JOIN sys.schemas s ON s.schema_id = tt.schema_id
|
439
|
+
WHERE QUOTENAME(s.name) = #{t.schema.sql_quoted}
|
440
|
+
AND QUOTENAME(tt.name) = #{t.name.sql_quoted}
|
441
|
+
AND QUOTENAME(c.name) = #{col.name.sql_quoted}
|
442
|
+
AND cc.definition = #{cstrt.expression.sql_quoted}
|
443
|
+
)
|
444
|
+
BEGIN
|
445
|
+
#{adoption_error_sql "Expected CHECK constraint on #{col.name} of #{t.qualified_name} for #{cstrt.expression} not present."}
|
446
|
+
END
|
447
|
+
})
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# Table constraints
|
452
|
+
t.constraints.select {|c| ['PRIMARY KEY', 'UNIQUE'].include? c.type}.each do |c|
|
453
|
+
tests << TableTypeKeyConstraintChecks.new(t, c, method(:adoption_error_sql)).to_s
|
454
|
+
end
|
455
|
+
t.constraints.select {|c| c.type == 'CHECK'}.each do |c|
|
456
|
+
tests << (dedent %Q{
|
457
|
+
IF NOT EXISTS (
|
458
|
+
SELECT * FROM sys.check_constraints cc
|
459
|
+
JOIN sys.table_types tt ON tt.type_table_object_id = cc.parent_object_id
|
460
|
+
JOIN sys.schemas s ON s.schema_id = tt.schema_id
|
461
|
+
WHERE QUOTENAME(s.name) = #{t.schema.sql_quoted}
|
462
|
+
AND QUOTENAME(tt.name) = #{t.name.sql_quoted}
|
463
|
+
AND cc.parent_column_id = 0
|
464
|
+
AND cc.definition = #{c.expression.sql_quoted}
|
465
|
+
)
|
466
|
+
BEGIN
|
467
|
+
#{adoption_error_sql "Expected CHECK constraint for #{c.expression} on #{t.qualified_name} not present."}
|
468
|
+
END
|
469
|
+
})
|
470
|
+
end
|
471
|
+
end.join("\n")
|
472
|
+
end
|
473
|
+
|
474
|
+
def check_clr_types
|
475
|
+
db_expectations.clr_types.map do |t|
|
476
|
+
dedent %Q{
|
477
|
+
IF NOT EXISTS (
|
478
|
+
SELECT * FROM sys.assembly_types t
|
479
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
480
|
+
WHERE QUOTENAME(s.name) = #{t.schema.sql_quoted} AND QUOTENAME(t.name) = #{t.name.sql_quoted}
|
481
|
+
)
|
482
|
+
BEGIN
|
483
|
+
#{adoption_error_sql "CLR type #{t.qualified_name} does not exist."}
|
484
|
+
END
|
485
|
+
|
486
|
+
IF NOT EXISTS (
|
487
|
+
SELECT * FROM sys.assembly_types t
|
488
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
489
|
+
JOIN sys.assemblies asm ON t.assembly_id = asm.assembly_id
|
490
|
+
WHERE QUOTENAME(s.name) = #{t.schema.sql_quoted} AND QUOTENAME(t.name) = #{t.name.sql_quoted}
|
491
|
+
AND QUOTENAME(asm.name) = #{t.assembly.sql_quoted}
|
492
|
+
)
|
493
|
+
BEGIN
|
494
|
+
#{adoption_error_sql "CLR type #{t.qualified_name} does not reference assembly #{t.assembly}."}
|
495
|
+
END
|
496
|
+
|
497
|
+
IF NOT EXISTS (
|
498
|
+
SELECT * FROM sys.assembly_types t
|
499
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
500
|
+
WHERE QUOTENAME(s.name) = #{t.schema.sql_quoted} AND QUOTENAME(t.name) = #{t.name.sql_quoted}
|
501
|
+
AND QUOTENAME(t.assembly_class) = #{t.clr_class.sql_quoted}
|
502
|
+
)
|
503
|
+
BEGIN
|
504
|
+
#{adoption_error_sql "CLR type #{t.qualified_name} does not reference class #{t.clr_class} of #{t.assembly}."}
|
505
|
+
END
|
506
|
+
}
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
def check_clr_aggregates
|
511
|
+
db_expectations.aggregates.map do |agg|
|
512
|
+
dedent %Q{
|
513
|
+
IF NOT EXISTS (
|
514
|
+
SELECT *
|
515
|
+
FROM sys.objects fn
|
516
|
+
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
517
|
+
WHERE fn.type = 'AF'
|
518
|
+
AND QUOTENAME(s.name) = #{agg.schema.sql_quoted}
|
519
|
+
AND QUOTENAME(fn.name) = #{agg.name.sql_quoted}
|
520
|
+
)
|
521
|
+
BEGIN
|
522
|
+
#{adoption_error_sql "CLR aggregate #{agg.qualified_name} does not exist."}
|
523
|
+
END
|
524
|
+
|
525
|
+
IF NOT EXISTS (
|
526
|
+
SELECT *
|
527
|
+
FROM sys.objects fn
|
528
|
+
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
529
|
+
JOIN sys.assembly_modules asmmod ON fn.object_id = asmmod.object_id
|
530
|
+
JOIN sys.assemblies asm ON asmmod.assembly_id = asm.assembly_id
|
531
|
+
WHERE fn.type = 'AF'
|
532
|
+
AND QUOTENAME(s.name) = #{agg.schema.sql_quoted}
|
533
|
+
AND QUOTENAME(fn.name) = #{agg.name.sql_quoted}
|
534
|
+
AND QUOTENAME(asm.name) = #{agg.clr_impl.assembly.sql_quoted}
|
535
|
+
)
|
536
|
+
BEGIN
|
537
|
+
#{adoption_error_sql "CLR aggregate #{agg.qualified_name} does not reference assembly #{agg.clr_impl.assembly}."}
|
538
|
+
END
|
539
|
+
|
540
|
+
IF NOT EXISTS (
|
541
|
+
SELECT *
|
542
|
+
FROM sys.objects fn
|
543
|
+
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
544
|
+
JOIN sys.assembly_modules asmmod ON fn.object_id = asmmod.object_id
|
545
|
+
JOIN sys.assemblies asm ON asmmod.assembly_id = asm.assembly_id
|
546
|
+
WHERE fn.type = 'AF'
|
547
|
+
AND QUOTENAME(s.name) = #{agg.schema.sql_quoted}
|
548
|
+
AND QUOTENAME(fn.name) = #{agg.name.sql_quoted}
|
549
|
+
AND QUOTENAME(asm.name) = #{agg.clr_impl.assembly.sql_quoted}
|
550
|
+
AND QUOTENAME(asmmod.assembly_class) = #{agg.clr_impl.asm_class.sql_quoted}
|
551
|
+
)
|
552
|
+
BEGIN
|
553
|
+
#{adoption_error_sql "CLR aggregate #{agg.qualified_name} does not reference class #{agg.clr_impl.asm_class} of #{agg.clr_impl.assembly}."}
|
554
|
+
END
|
555
|
+
|
556
|
+
IF NOT EXISTS (
|
557
|
+
SELECT *
|
558
|
+
FROM sys.objects fn
|
559
|
+
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
560
|
+
JOIN sys.assembly_modules asmmod ON fn.object_id = asmmod.object_id
|
561
|
+
WHERE fn.type = 'AF'
|
562
|
+
AND QUOTENAME(s.name) = #{agg.schema.sql_quoted}
|
563
|
+
AND QUOTENAME(fn.name) = #{agg.name.sql_quoted}
|
564
|
+
AND asmmod.execute_as_principal_id #{
|
565
|
+
case agg.execute_as
|
566
|
+
when nil
|
567
|
+
'IS NULL'
|
568
|
+
when 'OWNER'
|
569
|
+
"= -2"
|
570
|
+
else
|
571
|
+
"= DATABASE_PRINCIPAL_ID(#{agg.execute_as.sql_quoted})"
|
572
|
+
end
|
573
|
+
}
|
574
|
+
)
|
575
|
+
BEGIN
|
576
|
+
#{adoption_error_sql "CLR aggregate #{agg.qualified_name} does not execute as #{
|
577
|
+
case agg.execute_as
|
578
|
+
when nil
|
579
|
+
'CALLER'
|
580
|
+
else
|
581
|
+
agg.execute_as
|
582
|
+
end
|
583
|
+
}."}
|
584
|
+
END
|
204
585
|
}
|
205
586
|
end
|
206
587
|
end
|
@@ -230,7 +611,7 @@ module Mkxms::Mssql
|
|
230
611
|
def add_table_tests
|
231
612
|
dsl {
|
232
613
|
puts "IF NOT EXISTS (%s)" do
|
233
|
-
puts %Q{
|
614
|
+
puts dedent %Q{
|
234
615
|
SELECT * FROM sys.tables t
|
235
616
|
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
236
617
|
WHERE t.name = #{table_name_literal}
|
@@ -261,30 +642,30 @@ module Mkxms::Mssql
|
|
261
642
|
end
|
262
643
|
)
|
263
644
|
}
|
264
|
-
puts "END
|
645
|
+
puts "END"
|
265
646
|
puts
|
266
|
-
QueryCursor.new(
|
267
|
-
dedent(%Q{
|
268
|
-
SELECT c.object_id, c.column_id
|
269
|
-
FROM sys.columns c
|
270
|
-
INNER JOIN sys.tables t ON c.object_id = t.object_id
|
271
|
-
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
272
|
-
WHERE t.name = #{table_name_literal}
|
273
|
-
AND s.name = #{schema_name_literal}
|
274
|
-
ORDER BY c.column_id;
|
275
|
-
}),
|
276
|
-
"@column_object INT, @column_id INT",
|
277
|
-
output_to: self
|
278
|
-
).expectations(
|
279
|
-
on_extra: ->{puts error_sql "Table #{table_id} has one or more unexpected columns."},
|
280
|
-
) do |test|
|
281
|
-
table.columns.each do |column|
|
282
|
-
test.row(
|
283
|
-
on_missing: ->{puts error_sql "Column #{column.name} not found where expected in #{table_id}."},
|
284
|
-
) {add_column_tests(column)}
|
285
|
-
end
|
286
|
-
end
|
287
647
|
}
|
648
|
+
QueryCursor.new(
|
649
|
+
dedent(%Q{
|
650
|
+
SELECT c.object_id, c.column_id
|
651
|
+
FROM sys.columns c
|
652
|
+
INNER JOIN sys.tables t ON c.object_id = t.object_id
|
653
|
+
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
|
654
|
+
WHERE t.name = #{table_name_literal}
|
655
|
+
AND s.name = #{schema_name_literal}
|
656
|
+
ORDER BY c.column_id;
|
657
|
+
}),
|
658
|
+
"@column_object INT, @column_id INT",
|
659
|
+
output_to: self
|
660
|
+
).expectations(
|
661
|
+
on_extra: ->{puts error_sql "Table #{table_id} has one or more unexpected columns."},
|
662
|
+
) do |test|
|
663
|
+
table.columns.each do |column|
|
664
|
+
test.row(
|
665
|
+
on_missing: ->{puts error_sql "Column #{column.name} not found where expected in #{table_id}."},
|
666
|
+
) {add_column_tests(column)}
|
667
|
+
end
|
668
|
+
end
|
288
669
|
end
|
289
670
|
|
290
671
|
def add_column_tests(column)
|
@@ -317,11 +698,11 @@ module Mkxms::Mssql
|
|
317
698
|
indented {
|
318
699
|
puts error_sql "Column #{column.name} not found in expected position in #{table_id}."
|
319
700
|
}
|
320
|
-
puts "END
|
701
|
+
puts "END"
|
321
702
|
}
|
322
|
-
puts "END
|
703
|
+
puts "END"
|
323
704
|
puts "IF @column_id IS NOT NULL"
|
324
|
-
puts "BEGIN".."END
|
705
|
+
puts "BEGIN".."END" do
|
325
706
|
add_column_properties_test(column)
|
326
707
|
end
|
327
708
|
}
|
@@ -337,13 +718,13 @@ module Mkxms::Mssql
|
|
337
718
|
conditions << %Q{c.is_computed = 1}
|
338
719
|
conditions << compose_sql {
|
339
720
|
puts "EXISTS (SELECT * FROM sys.computed_columns cc WHERE %s)" do
|
340
|
-
puts "
|
721
|
+
puts "cc.object_id = c.object_id"
|
341
722
|
puts "AND cc.column_id = c.column_id"
|
342
723
|
puts "AND cc.definition = %s" do
|
343
|
-
strlit(column.computed_expression)
|
724
|
+
puts strlit(column.computed_expression)
|
344
725
|
end
|
345
726
|
puts "AND %s" do
|
346
|
-
bit_test "cc.is_persisted", column.persisted?
|
727
|
+
puts(bit_test "cc.is_persisted", column.persisted?)
|
347
728
|
end
|
348
729
|
end
|
349
730
|
}
|
@@ -396,7 +777,7 @@ module Mkxms::Mssql
|
|
396
777
|
}
|
397
778
|
conditions.each {|c| puts "AND " + c, :sub => nil}
|
398
779
|
end
|
399
|
-
puts "BEGIN".."END
|
780
|
+
puts "BEGIN".."END" do
|
400
781
|
puts error_sql "Column #{column.name} of #{table_id} #{mismatch_message}"
|
401
782
|
end
|
402
783
|
}
|
@@ -444,11 +825,11 @@ module Mkxms::Mssql
|
|
444
825
|
puts strlit(unquoted_identifier col_dflt.name)
|
445
826
|
end if col_dflt.name
|
446
827
|
end
|
447
|
-
puts("BEGIN".."END
|
828
|
+
puts("BEGIN".."END") {
|
448
829
|
puts adoption_error_sql("Column default constraint #{constraint_id} does not have the expected definition.")
|
449
830
|
}
|
450
831
|
}
|
451
|
-
puts "END
|
832
|
+
puts "END"
|
452
833
|
}
|
453
834
|
end.join("\n")
|
454
835
|
end
|
@@ -533,7 +914,7 @@ module Mkxms::Mssql
|
|
533
914
|
|
534
915
|
check_column_sequence_end
|
535
916
|
}
|
536
|
-
puts "END
|
917
|
+
puts "END"
|
537
918
|
}
|
538
919
|
end
|
539
920
|
|
@@ -582,17 +963,17 @@ module Mkxms::Mssql
|
|
582
963
|
IF @constraint_match_error = 0
|
583
964
|
BEGIN
|
584
965
|
SET @constraint_found = 1;
|
585
|
-
END
|
966
|
+
END
|
586
967
|
}
|
587
968
|
}
|
588
|
-
puts "END
|
969
|
+
puts "END"
|
589
970
|
puts dedent %Q{
|
590
971
|
CLOSE constraint_cursor;
|
591
972
|
DEALLOCATE constraint_cursor;
|
592
973
|
|
593
974
|
IF @constraint_found = 0
|
594
975
|
}
|
595
|
-
puts "BEGIN".."END
|
976
|
+
puts "BEGIN".."END" do
|
596
977
|
puts error_sql "Expected #{cnstr_id} does not exist."
|
597
978
|
end
|
598
979
|
}
|
@@ -620,7 +1001,7 @@ module Mkxms::Mssql
|
|
620
1001
|
FETCH NEXT FROM column_cursor INTO @column_name, @column_sorted_descending;
|
621
1002
|
IF @@FETCH_STATUS = 0
|
622
1003
|
}
|
623
|
-
puts "BEGIN".."END
|
1004
|
+
puts "BEGIN".."END" do
|
624
1005
|
puts error_sql "#{cnstr_id.capitalize} has one or more unexpected columns."
|
625
1006
|
end
|
626
1007
|
puts "CLOSE column_cursor;"
|
@@ -654,7 +1035,7 @@ module Mkxms::Mssql
|
|
654
1035
|
indented {
|
655
1036
|
yield "Column #{index_column.name} should be sorted #{index_column.direction} in #{cnstr_id}."
|
656
1037
|
}
|
657
|
-
puts "END
|
1038
|
+
puts "END"
|
658
1039
|
}
|
659
1040
|
end
|
660
1041
|
end
|
@@ -904,7 +1285,7 @@ module Mkxms::Mssql
|
|
904
1285
|
indented {
|
905
1286
|
puts error_sql "#{cnstr_id.capitalize} does not have expected definition."
|
906
1287
|
}
|
907
|
-
puts "END
|
1288
|
+
puts "END"
|
908
1289
|
}
|
909
1290
|
end
|
910
1291
|
|
@@ -921,7 +1302,7 @@ module Mkxms::Mssql
|
|
921
1302
|
AND cc.definition = #{strlit cnstr.definition}
|
922
1303
|
}
|
923
1304
|
end
|
924
|
-
puts "BEGIN".."END
|
1305
|
+
puts "BEGIN".."END" do
|
925
1306
|
puts error_sql "Expected #{cnstr_id} does not exist."
|
926
1307
|
end
|
927
1308
|
}
|
@@ -934,6 +1315,251 @@ module Mkxms::Mssql
|
|
934
1315
|
end # Do not join -- each needs a separate batch (they use variables)
|
935
1316
|
end
|
936
1317
|
|
1318
|
+
class DmlTriggerAdoptionChecks < IndentedStringBuilder
|
1319
|
+
include SqlStringManipulators
|
1320
|
+
|
1321
|
+
def initialize(trigger, tools)
|
1322
|
+
super()
|
1323
|
+
|
1324
|
+
@tools = tools
|
1325
|
+
@trigger = trigger
|
1326
|
+
|
1327
|
+
add_trigger_tests
|
1328
|
+
if trigger.clr_impl
|
1329
|
+
add_clr_impl_test
|
1330
|
+
else
|
1331
|
+
add_definition_test
|
1332
|
+
end
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
attr_reader :trigger
|
1336
|
+
|
1337
|
+
def error_sql(s)
|
1338
|
+
@tools.adoption_error_sql(s)
|
1339
|
+
end
|
1340
|
+
|
1341
|
+
def check_definition_is(*args)
|
1342
|
+
@tools.definition_matches_by_hash(*args)
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
def add_trigger_tests
|
1346
|
+
dsl {
|
1347
|
+
puts "IF NOT EXISTS (%s)" do
|
1348
|
+
puts dedent %Q{
|
1349
|
+
SELECT * FROM sys.triggers tgr
|
1350
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1351
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1352
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1353
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1354
|
+
}
|
1355
|
+
end
|
1356
|
+
puts "BEGIN".."END" do
|
1357
|
+
puts error_sql "Expected trigger #{trigger.qualified_name} does not exist."
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
puts "IF NOT EXISTS (%s)" do
|
1361
|
+
puts dedent %Q{
|
1362
|
+
SELECT * FROM sys.triggers tgr
|
1363
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1364
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1365
|
+
JOIN sys.tables t ON tgr.parent_id = t.object_id
|
1366
|
+
JOIN sys.schemas ts ON t.schema_id = ts.schema_id
|
1367
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1368
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1369
|
+
AND QUOTENAME(ts.name) = #{trigger.table.schema.sql_quoted}
|
1370
|
+
AND QUOTENAME(t.name) = #{trigger.table.name.sql_quoted}
|
1371
|
+
}
|
1372
|
+
end
|
1373
|
+
puts "BEGIN".."END" do
|
1374
|
+
puts error_sql "Trigger #{trigger.qualified_name} does not apply to table #{trigger.table.qualified_name}."
|
1375
|
+
end
|
1376
|
+
|
1377
|
+
puts "IF NOT EXISTS (%s)" do
|
1378
|
+
execution_identity_test = (if trigger.execute_as == 'OWNER'
|
1379
|
+
"COALESCE(sql.execute_as_principal_id, asmmod.execute_as_principal_id) = -2"
|
1380
|
+
elsif trigger.execute_as
|
1381
|
+
"(QUOTENAME(p.name) = #{trigger.execute_as.sql_quoted} OR QUOTENAME(p2.name) = #{trigger.execute_as.sql_quoted})"
|
1382
|
+
else
|
1383
|
+
"COALESCE(sql.execute_as_principal_id, asmmod.execute_as_principal_id) IS NULL"
|
1384
|
+
end)
|
1385
|
+
|
1386
|
+
puts dedent %Q{
|
1387
|
+
SELECT * FROM sys.triggers tgr
|
1388
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1389
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1390
|
+
LEFT JOIN sys.sql_modules sql ON tgr.object_id = sql.object_id
|
1391
|
+
LEFT JOIN sys.database_principals p ON p.principal_id = sql.execute_as_principal_id
|
1392
|
+
LEFT JOIN sys.assembly_modules asmmod ON tgr.object_id = asmmod.object_id
|
1393
|
+
LEFT JOIN sys.database_principals pa ON pa.principal_id = asmmod.execute_as_principal_id
|
1394
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1395
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1396
|
+
AND #{execution_identity_test}
|
1397
|
+
}
|
1398
|
+
end
|
1399
|
+
puts "BEGIN".."END" do
|
1400
|
+
if trigger.execute_as == 'OWNER'
|
1401
|
+
puts error_sql "Trigger #{trigger.qualified_name} does not execute as its owner."
|
1402
|
+
elsif trigger.execute_as
|
1403
|
+
puts error_sql "Trigger #{trigger.qualified_name} does not execute as #{trigger.execute_as}."
|
1404
|
+
else
|
1405
|
+
puts error_sql "Trigger #{trigger.qualified_name} does not execute as caller."
|
1406
|
+
end
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
puts "IF NOT EXISTS (%s)" do
|
1410
|
+
is_instead_of_trigger_value = (trigger.timing.downcase == "after") ? 0 : 1
|
1411
|
+
|
1412
|
+
puts dedent %Q{
|
1413
|
+
SELECT * FROM sys.triggers tgr
|
1414
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1415
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1416
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1417
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1418
|
+
AND tgr.is_instead_of_trigger = #{is_instead_of_trigger_value}
|
1419
|
+
}
|
1420
|
+
end
|
1421
|
+
puts "BEGIN".."END" do
|
1422
|
+
puts error_sql %Q{Trigger #{trigger.qualified_name} must occur #{trigger.timing} the handled event(s).}
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
puts "IF NOT EXISTS (%s)" do
|
1426
|
+
puts dedent %Q{
|
1427
|
+
SELECT * FROM sys.triggers tgr
|
1428
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1429
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1430
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1431
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1432
|
+
}
|
1433
|
+
trigger.events.each do |ev|
|
1434
|
+
puts dedent %Q{
|
1435
|
+
AND EXISTS (
|
1436
|
+
SELECT *
|
1437
|
+
FROM sys.events ev
|
1438
|
+
WHERE ev.object_id = tgr.object_id
|
1439
|
+
AND ev.type_desc = #{ev.sql_quoted}
|
1440
|
+
)
|
1441
|
+
}
|
1442
|
+
end
|
1443
|
+
end
|
1444
|
+
puts "BEGIN".."END" do
|
1445
|
+
puts error_sql %Q{Trigger #{trigger.qualified_name} must occur #{trigger.timing.downcase} #{trigger.events.join(' and ')}.}
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
puts "IF NOT EXISTS (%s)" do
|
1449
|
+
is_not_for_replication_value = trigger.not_replicable ? 1 : 0
|
1450
|
+
|
1451
|
+
puts dedent %Q{
|
1452
|
+
SELECT * FROM sys.triggers tgr
|
1453
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1454
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1455
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1456
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1457
|
+
AND tgr.is_not_for_replication = #{is_not_for_replication_value}
|
1458
|
+
}
|
1459
|
+
end
|
1460
|
+
puts "BEGIN".."END" do
|
1461
|
+
puts error_sql %Q{Trigger #{trigger.qualified_name} must#{' not' unless trigger.not_replicable} be configured "NOT FOR REPLICATION".}
|
1462
|
+
end
|
1463
|
+
|
1464
|
+
puts "IF NOT EXISTS (%s)" do
|
1465
|
+
is_disabled_value = trigger.disabled ? 1 : 0
|
1466
|
+
|
1467
|
+
puts dedent %Q{
|
1468
|
+
SELECT * FROM sys.triggers tgr
|
1469
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1470
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1471
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1472
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1473
|
+
AND tgr.is_disabled = #{is_disabled_value}
|
1474
|
+
}
|
1475
|
+
end
|
1476
|
+
puts "BEGIN".."END" do
|
1477
|
+
puts error_sql "Trigger #{trigger.qualified_name} must#{' not' unless trigger.disabled} be disabled."
|
1478
|
+
end
|
1479
|
+
}
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
def add_clr_impl_test
|
1483
|
+
dsl {
|
1484
|
+
# Check CLR implementation
|
1485
|
+
puts "IF NOT EXISTS(%s)" do
|
1486
|
+
puts dedent %Q{
|
1487
|
+
SELECT * FROM sys.triggers tgr
|
1488
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1489
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1490
|
+
JOIN sys.assembly_modules asmmod ON tgr.object_id = asmmod.object_id
|
1491
|
+
JOIN sys.assemblies asm ON asmmod.assembly_id = asm.assembly_id
|
1492
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1493
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1494
|
+
AND QUOTENAME(asm.name) = #{trigger.clr_impl.assembly.sql_quoted}
|
1495
|
+
AND QUOTENAME(asmmod.assembly_class) = #{trigger.clr_impl.asm_class.sql_quoted}
|
1496
|
+
AND QUOTENAME(asmmod.assembly_method) = #{trigger.clr_impl.method.sql_quoted}
|
1497
|
+
}
|
1498
|
+
end
|
1499
|
+
puts "BEGIN".."END" do
|
1500
|
+
puts error_sql "Trigger #{trigger.qualified_name} does not invoke #{trigger.clr_impl.full_specifier}."
|
1501
|
+
end
|
1502
|
+
}
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
def add_definition_test
|
1506
|
+
dsl {
|
1507
|
+
# Check definition
|
1508
|
+
puts "IF NOT EXISTS (%s)" do
|
1509
|
+
puts dedent %Q{
|
1510
|
+
SELECT * FROM sys.triggers tgr
|
1511
|
+
JOIN sys.objects o ON tgr.object_id = o.object_id
|
1512
|
+
JOIN sys.schemas s ON o.schema_id = s.schema_id
|
1513
|
+
JOIN sys.sql_modules sql ON tgr.object_id = sql.object_id
|
1514
|
+
WHERE QUOTENAME(s.name) = #{trigger.schema.sql_quoted}
|
1515
|
+
AND QUOTENAME(tgr.name) = #{trigger.name.sql_quoted}
|
1516
|
+
AND #{check_definition_is("sql.definition", trigger.definition)}
|
1517
|
+
}
|
1518
|
+
end
|
1519
|
+
puts "BEGIN".."END" do
|
1520
|
+
puts error_sql "Trigger #{trigger.qualified_name} does not have the expected definition."
|
1521
|
+
end
|
1522
|
+
}
|
1523
|
+
end
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
def check_dml_triggers
|
1527
|
+
db_expectations.dml_triggers.map do |tgr|
|
1528
|
+
DmlTriggerAdoptionChecks.new(tgr, self).to_s
|
1529
|
+
end
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
def check_synonyms
|
1533
|
+
db_expectations.synonyms.map do |syn|
|
1534
|
+
dedent %Q{
|
1535
|
+
IF NOT EXISTS (
|
1536
|
+
SELECT * FROM sys.synonyms syn
|
1537
|
+
JOIN sys.schemas s ON syn.schema_id = s.schema_id
|
1538
|
+
WHERE QUOTENAME(s.name) = #{syn.schema.sql_quoted}
|
1539
|
+
AND QUOTENAME(syn.name) = #{syn.name.sql_quoted}
|
1540
|
+
)
|
1541
|
+
BEGIN
|
1542
|
+
#{adoption_error_sql "Synonym #{syn.qualified_name} does not exist."}
|
1543
|
+
END
|
1544
|
+
|
1545
|
+
IF NOT EXISTS (
|
1546
|
+
SELECT * FROM sys.synonyms syn
|
1547
|
+
JOIN sys.schemas s ON syn.schema_id = s.schema_id
|
1548
|
+
WHERE QUOTENAME(s.name) = #{syn.schema.sql_quoted}
|
1549
|
+
AND QUOTENAME(syn.name) = #{syn.name.sql_quoted}
|
1550
|
+
AND (
|
1551
|
+
OBJECT_ID(syn.base_object_name) IS NULL
|
1552
|
+
OR
|
1553
|
+
OBJECT_ID(syn.base_object_name) = OBJECT_ID(#{syn.referent.sql_quoted})
|
1554
|
+
)
|
1555
|
+
)
|
1556
|
+
BEGIN
|
1557
|
+
#{adoption_error_sql "Synonym #{syn.qualified_name} does not reference #{syn.referent}."}
|
1558
|
+
END
|
1559
|
+
}
|
1560
|
+
end
|
1561
|
+
end
|
1562
|
+
|
937
1563
|
class IndexAdoptionChecks < IndentedStringBuilder
|
938
1564
|
include SqlStringManipulators
|
939
1565
|
|
@@ -983,7 +1609,7 @@ module Mkxms::Mssql
|
|
983
1609
|
# Key columns
|
984
1610
|
QueryCursor.new(
|
985
1611
|
dedent(%Q{
|
986
|
-
SELECT c.
|
1612
|
+
SELECT c.name, ic.is_descending_key
|
987
1613
|
FROM sys.index_columns ic
|
988
1614
|
JOIN sys.columns c
|
989
1615
|
ON ic.object_id = c.object_id
|
@@ -1007,29 +1633,31 @@ module Mkxms::Mssql
|
|
1007
1633
|
indented {
|
1008
1634
|
puts error_sql "Expected #{column.name} as column #{i + 1} in #{index_id}."
|
1009
1635
|
}
|
1010
|
-
puts "END ELSE IF #{bit_test('@is_sorted_descending', column.direction
|
1636
|
+
puts "END ELSE IF #{bit_test('@is_sorted_descending', column.direction != :descending)}"
|
1637
|
+
puts "BEGIN"
|
1011
1638
|
indented {
|
1012
1639
|
puts error_sql "Expected #{column.name} to be sorted #{column.direction} in #{index_id}."
|
1013
1640
|
}
|
1014
|
-
puts "END
|
1641
|
+
puts "END"
|
1015
1642
|
}
|
1016
1643
|
end
|
1017
1644
|
end
|
1018
1645
|
|
1019
1646
|
# Included columns
|
1020
|
-
included_column_names = index.included_columns.map {|c| c.name}
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1647
|
+
unless (included_column_names = index.included_columns.map {|c| c.name}).empty?
|
1648
|
+
puts "IF (%s) < #{included_column_names.length}" do
|
1649
|
+
puts dedent %Q{
|
1650
|
+
SELECT COUNT(*) FROM sys.index_columns ic
|
1651
|
+
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
|
1652
|
+
WHERE ic.object_id = @relation_id
|
1653
|
+
AND ic.index_id = @index_id
|
1654
|
+
AND ic.key_ordinal = 0
|
1655
|
+
AND QUOTENAME(c.name) IN (#{included_column_names.map {|s| strlit s}.join(', ')})
|
1656
|
+
}
|
1657
|
+
end
|
1658
|
+
puts "BEGIN".."END" do
|
1659
|
+
puts error_sql "#{index_id.capitalize} is missing one or more expected included columns."
|
1660
|
+
end
|
1033
1661
|
end
|
1034
1662
|
}
|
1035
1663
|
|
@@ -1038,7 +1666,7 @@ module Mkxms::Mssql
|
|
1038
1666
|
|
1039
1667
|
def index_property_check(expectation, expectation_desc)
|
1040
1668
|
%Q{
|
1041
|
-
IF NOT
|
1669
|
+
IF NOT EXISTS (
|
1042
1670
|
SELECT * FROM sys.indexes i
|
1043
1671
|
WHERE i.object_id = @relation_id
|
1044
1672
|
AND i.index_id = @index_id
|
@@ -1046,7 +1674,7 @@ module Mkxms::Mssql
|
|
1046
1674
|
)
|
1047
1675
|
BEGIN
|
1048
1676
|
#{error_sql "#{@index_id.capitalize} should #{expectation_desc}."}
|
1049
|
-
END
|
1677
|
+
END
|
1050
1678
|
}.strip.gsub(/\s+/, ' ')
|
1051
1679
|
end
|
1052
1680
|
|
@@ -1128,7 +1756,7 @@ module Mkxms::Mssql
|
|
1128
1756
|
end
|
1129
1757
|
end
|
1130
1758
|
}
|
1131
|
-
puts "END
|
1759
|
+
puts "END"
|
1132
1760
|
}
|
1133
1761
|
end
|
1134
1762
|
|
@@ -1153,8 +1781,23 @@ module Mkxms::Mssql
|
|
1153
1781
|
"INSERT INTO [xmigra].[access_objects] ([type], [name]) VALUES (N'#{type}', #{strlit qualified_name});"
|
1154
1782
|
end
|
1155
1783
|
|
1156
|
-
|
1157
|
-
|
1784
|
+
# *indent* gives indentation for 2nd and later lines, or may be +false+ to
|
1785
|
+
# put all hash comparisons on the same line
|
1786
|
+
#
|
1787
|
+
# *** NEW IMPLEMENTATION IGNORES WHITESPACE WHEN COMPARING FUNCTION DEFINITIONS ***
|
1788
|
+
def definition_matches_by_hash(expr, definition, indent: ' ')
|
1789
|
+
digest_alg = Digest::MD5
|
1790
|
+
defn_reduced = definition.gsub(/\r|\n| /, "").encode(Encoding::UTF_16LE)
|
1791
|
+
(0..defn_reduced.length).step(4000).map do |start|
|
1792
|
+
begin
|
1793
|
+
digest_alg.hexdigest(defn_reduced[start, 4000])
|
1794
|
+
rescue Exception
|
1795
|
+
require 'pry'; binding.pry unless $STOP_PRYING || ($STOP_PRYING_FOR || {})[:context]
|
1796
|
+
raise
|
1797
|
+
end
|
1798
|
+
hash = digest_alg.hexdigest(defn_reduced[start, 4000])
|
1799
|
+
"HASHBYTES('md5', SUBSTRING(REPLACE(REPLACE(REPLACE(#{expr}, NCHAR(10), ''), NCHAR(13), ''), ' ', ''), #{start + 1}, 4000)) = 0x#{hash}"
|
1800
|
+
end.join("#{indent ? "\n" + indent : ' '}AND ")
|
1158
1801
|
end
|
1159
1802
|
|
1160
1803
|
def adopt_views_sql
|
@@ -1186,7 +1829,7 @@ module Mkxms::Mssql
|
|
1186
1829
|
indented {
|
1187
1830
|
puts adoption_error_sql "View #{view.qualified_name} does not have the expected definition."
|
1188
1831
|
}
|
1189
|
-
puts "END
|
1832
|
+
puts "END"
|
1190
1833
|
puts access_object_adoption_sql(:VIEW, view.qualified_name)
|
1191
1834
|
}
|
1192
1835
|
end
|
@@ -1208,20 +1851,31 @@ module Mkxms::Mssql
|
|
1208
1851
|
puts adoption_error_sql "Stored procedure #{sproc.qualified_name} does not exist."
|
1209
1852
|
}
|
1210
1853
|
puts "END ELSE IF NOT EXISTS (%s)" do
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1854
|
+
if sproc.clr_impl
|
1855
|
+
puts dedent %Q{
|
1856
|
+
SELECT * FROM sys.objects sproc
|
1857
|
+
JOIN sys.assembly_modules asmmod ON sproc.object_id = asmmod.object_id
|
1858
|
+
JOIN sys.assemblies asm ON asmmod.assembly_id = asm.assembly_id
|
1859
|
+
JOIN sys.schemas s ON sproc.schema_id = s.schema_id
|
1860
|
+
WHERE sproc.type = 'PC'
|
1861
|
+
AND QUOTENAME(asm.name) = #{sproc.clr_impl.assembly.sql_quoted}
|
1862
|
+
AND QUOTENAME(asmmod.assembly_class) = #{sproc.clr_impl.asm_class.sql_quoted}
|
1863
|
+
AND QUOTENAME(asmmod.assembly_method) = #{sproc.clr_impl.method.sql_quoted}
|
1864
|
+
}
|
1865
|
+
else
|
1866
|
+
puts dedent %Q{
|
1867
|
+
SELECT * FROM sys.procedures p
|
1868
|
+
JOIN sys.schemas s ON p.schema_id = s.schema_id
|
1869
|
+
JOIN sys.sql_modules sql ON p.object_id = sql.object_id
|
1870
|
+
WHERE s.name = #{strlit(unquoted_identifier sproc.schema)}
|
1871
|
+
AND p.name = #{strlit(unquoted_identifier sproc.name)}
|
1872
|
+
AND #{definition_matches_by_hash('sql.definition', sproc.definition)}
|
1873
|
+
}
|
1874
|
+
end
|
1219
1875
|
end
|
1220
|
-
puts "BEGIN"
|
1221
|
-
indented {
|
1876
|
+
puts "BEGIN"..."END" do
|
1222
1877
|
puts adoption_error_sql "Stored procedure #{sproc.qualified_name} does not have the expected definition."
|
1223
|
-
|
1224
|
-
puts "END;"
|
1878
|
+
end
|
1225
1879
|
puts access_object_adoption_sql(:PROCEDURE, sproc.qualified_name)
|
1226
1880
|
}
|
1227
1881
|
end
|
@@ -1236,7 +1890,7 @@ module Mkxms::Mssql
|
|
1236
1890
|
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
1237
1891
|
WHERE s.name = #{strlit(unquoted_identifier udf.schema)}
|
1238
1892
|
AND fn.name = #{strlit(unquoted_identifier udf.name)}
|
1239
|
-
AND fn.type IN ('FN', 'IF', 'TF')
|
1893
|
+
AND fn.type IN ('FN', 'FS', 'FT', 'IF', 'TF')
|
1240
1894
|
}
|
1241
1895
|
end
|
1242
1896
|
puts "BEGIN"
|
@@ -1244,20 +1898,34 @@ module Mkxms::Mssql
|
|
1244
1898
|
puts adoption_error_sql "Function #{udf.qualified_name} does not exist."
|
1245
1899
|
}
|
1246
1900
|
puts "END ELSE IF NOT EXISTS (%s)" do
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1901
|
+
if udf.clr_impl
|
1902
|
+
puts dedent %Q{
|
1903
|
+
SELECT * FROM sys.objects fn
|
1904
|
+
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
1905
|
+
JOIN sys.assembly_modules asmmod ON fn.object_id = asmmod.object_id
|
1906
|
+
JOIN sys.assemblies asm ON asmmod.assembly_id = asm.assembly_id
|
1907
|
+
WHERE QUOTENAME(s.name) = #{udf.schema.sql_quoted}
|
1908
|
+
AND QUOTENAME(fn.name) = #{udf.name.sql_quoted}
|
1909
|
+
AND QUOTENAME(asm.name) = #{udf.clr_impl.assembly.sql_quoted}
|
1910
|
+
AND QUOTENAME(asmmod.assembly_class) = #{udf.clr_impl.asm_class.sql_quoted}
|
1911
|
+
AND QUOTENAME(asmmod.assembly_method) = #{udf.clr_impl.method.sql_quoted}
|
1912
|
+
}
|
1913
|
+
else
|
1914
|
+
puts dedent %Q{
|
1915
|
+
SELECT * FROM sys.objects fn
|
1916
|
+
JOIN sys.schemas s ON fn.schema_id = s.schema_id
|
1917
|
+
JOIN sys.sql_modules sql ON fn.object_id = sql.object_id
|
1918
|
+
WHERE s.name = #{strlit(unquoted_identifier udf.schema)}
|
1919
|
+
AND fn.name = #{strlit(unquoted_identifier udf.name)}
|
1920
|
+
AND #{definition_matches_by_hash 'sql.definition', udf.definition}
|
1921
|
+
}
|
1922
|
+
end
|
1255
1923
|
end
|
1256
1924
|
puts "BEGIN"
|
1257
1925
|
indented {
|
1258
1926
|
puts adoption_error_sql "Function #{udf.qualified_name} does not have the expected definition."
|
1259
1927
|
}
|
1260
|
-
puts "END
|
1928
|
+
puts "END"
|
1261
1929
|
puts access_object_adoption_sql(:FUNCTION, udf.qualified_name)
|
1262
1930
|
}
|
1263
1931
|
end
|