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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -10
  3. data/lib/mkxms/mssql.rb +18 -0
  4. data/lib/mkxms/mssql/adoption_script_writer.rb +759 -91
  5. data/lib/mkxms/mssql/clr_aggregate_handler.rb +98 -0
  6. data/lib/mkxms/mssql/clr_assembly_handler.rb +92 -0
  7. data/lib/mkxms/mssql/clr_function_handler.rb +172 -0
  8. data/lib/mkxms/mssql/clr_impl.rb +58 -0
  9. data/lib/mkxms/mssql/clr_stored_procedure_handler.rb +88 -0
  10. data/lib/mkxms/mssql/clr_type_handler.rb +92 -0
  11. data/lib/mkxms/mssql/database_handler.rb +124 -3
  12. data/lib/mkxms/mssql/declaratives_creator.rb +206 -0
  13. data/lib/mkxms/mssql/dml_trigger_handler.rb +107 -0
  14. data/lib/mkxms/mssql/filegroup_handler.rb +1 -4
  15. data/lib/mkxms/mssql/function_handler.rb +1 -4
  16. data/lib/mkxms/mssql/indented_string_builder.rb +8 -2
  17. data/lib/mkxms/mssql/index_handler.rb +1 -4
  18. data/lib/mkxms/mssql/keywords.rb +492 -0
  19. data/lib/mkxms/mssql/primary_key_handler.rb +1 -4
  20. data/lib/mkxms/mssql/property_handler.rb +8 -0
  21. data/lib/mkxms/mssql/query_cursor.rb +12 -4
  22. data/lib/mkxms/mssql/references_handler.rb +24 -0
  23. data/lib/mkxms/mssql/role_handler.rb +1 -4
  24. data/lib/mkxms/mssql/scalar_type_handler.rb +108 -0
  25. data/lib/mkxms/mssql/schema_handler.rb +1 -4
  26. data/lib/mkxms/mssql/sql_string_manipulators.rb +4 -4
  27. data/lib/mkxms/mssql/statistics_handler.rb +1 -4
  28. data/lib/mkxms/mssql/stored_procedure_handler.rb +1 -4
  29. data/lib/mkxms/mssql/synonym_handler.rb +40 -0
  30. data/lib/mkxms/mssql/table_handler.rb +2 -8
  31. data/lib/mkxms/mssql/table_type_handler.rb +254 -0
  32. data/lib/mkxms/mssql/utils.rb +96 -0
  33. data/lib/mkxms/mssql/version.rb +1 -1
  34. data/lib/mkxms/mssql/view_handler.rb +1 -4
  35. data/spec/utils/indented_string_builder_spec.rb +21 -0
  36. data/spec/utils/query_cursor_spec.rb +2 -2
  37. data/spec/utils/sql_string_manipulators_spec.rb +59 -0
  38. metadata +18 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b79eb5cd26ad73ff11edc3d488344e569a723439
4
- data.tar.gz: d2dd1a1381d3d715b2dd5dfb066c7aed4f7f4b6e
3
+ metadata.gz: acbe79b89bb652174600791fc2290a37d6bbd19a
4
+ data.tar.gz: a1d4a0b1871fd80f0bb28e7d8eeed9f1b36f626d
5
5
  SHA512:
6
- metadata.gz: 9ad174edd9c865f2c5687567dd98a7a66bd38559ba0fc28715768d4dd03dc24d0830d8ff6baa5e214f6f7c97682bdd7e92eb9f206f7a14ecf1e1fb89b5ddc493
7
- data.tar.gz: 1fa9637d1da5cc828ef1d67f886144a98f6143433e5802a36af73117d03ed1c9b3e30b06da95edd8b3db567340a7d810aebb9ad1bd0dfb082863874e4a4bd284
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#file-mssql-eyewkas-sql)
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.0, the following
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;" do
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 "AND cc.object_id = c.object_id"
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;" do
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;" do
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;" do
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;" do
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.column_name, ic.is_descending_key
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 == :descending)}"
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
- puts "IF (%s) < #{included_column_names.length}" do
1022
- puts dedent %Q{
1023
- SELECT COUNT(*) FROM sys.index_columns ic
1024
- JOIN sys.columns c ON ic.object_id = c.object_id AND ic.index_id = c.index_id
1025
- WHERE ic.object_id = @relation_id
1026
- AND ic.index_id = @index_id
1027
- AND ic.key_ordinal = 0
1028
- AND QUOTENAME(c.name) IN (#{included_column_names.map {|s| strlit s}.join(', ')})
1029
- }
1030
- end
1031
- puts "BEGIN".."END" do
1032
- puts error_sql "#{index_id.capitalize} is missing one or more expected included columns."
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 EXIST (
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
- def definition_matches_by_hash(expr, definition)
1157
- "HASHBYTES('md5', #{expr}) = 0x#{Digest::MD5.hexdigest definition.gsub("\n", "\r\n").encode('UTF-16LE')}"
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
- puts dedent %Q{
1212
- SELECT * FROM sys.procedures p
1213
- JOIN sys.schemas s ON p.schema_id = s.schema_id
1214
- JOIN sys.sql_modules sql ON p.object_id = sql.object_id
1215
- WHERE s.name = #{strlit(unquoted_identifier sproc.schema)}
1216
- AND p.name = #{strlit(unquoted_identifier sproc.name)}
1217
- AND #{definition_matches_by_hash('sql.definition', sproc.definition)}
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
- puts dedent %Q{
1248
- SELECT * FROM sys.objects fn
1249
- JOIN sys.schemas s ON fn.schema_id = s.schema_id
1250
- JOIN sys.sql_modules sql ON fn.object_id = sql.object_id
1251
- WHERE s.name = #{strlit(unquoted_identifier udf.schema)}
1252
- AND fn.name = #{strlit(unquoted_identifier udf.name)}
1253
- AND #{definition_matches_by_hash 'sql.definition', udf.definition}
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