mkxms-mssql 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Gem Version](https://badge.fury.io/rb/mkxms-mssql.svg)](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
|