mkxms-mssql 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,98 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/clr_impl'
3
+ require 'mkxms/mssql/utils'
4
+
5
+ module Mkxms; end
6
+
7
+ module Mkxms::Mssql
8
+ class ClrAggregate
9
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
10
+ include Utils::SchemaQualifiedName
11
+ extend Utils::InitializedAttributes
12
+
13
+ SQL_OBJECT_TYPE = 'AGGREGATE'
14
+
15
+ def initialize(attrs)
16
+ @schema = attrs['schema']
17
+ @name = attrs['name']
18
+ @execute_as = attrs['execute-as']
19
+ end
20
+
21
+ attr_accessor :schema, :name, :execute_as, :clr_impl, :returns
22
+ attr_init(:params) {[]}
23
+
24
+ def to_sql
25
+ (procedure_def_sql + extended_properties_sql + param_properties_sql)
26
+ end
27
+
28
+ def procedure_def_sql
29
+ [[].tap do |lines|
30
+ lines << "IF NOT EXISTS ("
31
+ lines << " SELECT * FROM xmigra.ignored_clr_assemblies asm"
32
+ lines << " WHERE asm.name = #{clr_impl.assembly.sql_quoted}"
33
+ lines << ")"
34
+ lines << "CREATE AGGREGATE [{filename}] ("
35
+ lines << params.map do |param|
36
+ " #{param.name} #{param.type_spec}".tap do |param_spec|
37
+ param_spec << " = #{param.default_value}" if param.default_value
38
+ end
39
+ end.join(",\n")
40
+ lines << ")"
41
+ lines << "RETURNS #{returns.type_spec}" if returns
42
+ lines << "EXTERNAL NAME #{clr_impl.full_specifier};"
43
+ end.join("\n")]
44
+ end
45
+
46
+ def param_properties_sql
47
+ params.map do |param|
48
+ subitem_extended_properties_sql(param)
49
+ end
50
+ end
51
+ end
52
+
53
+ class ClrArggregateHandler
54
+ include PropertyHandler::ElementHandler
55
+
56
+ def initialize(aggregates, node)
57
+ a = node.attributes
58
+
59
+ @aggregate = ClrAggregate.new(a).tap do |agg|
60
+ store_properties_on agg
61
+ aggregates << agg
62
+ end
63
+ end
64
+
65
+ def handle_implementation_element(parse)
66
+ a = parse.node.attributes
67
+ @aggregate.clr_impl = ClrClass.new(a['assembly'], a['class'])
68
+ end
69
+
70
+ def handle_parameter_element(parse)
71
+ a = parse.node.attributes
72
+ Parameter.new(
73
+ a['name'],
74
+ a['type-schema'],
75
+ a['type'],
76
+ a['capacity'],
77
+ a['precision'],
78
+ a['scale'],
79
+ a['default'],
80
+ a['output'],
81
+ ).tap do |param|
82
+ @aggregate.params << param
83
+ parse.context = ParameterHandler.new(param)
84
+ end
85
+ end
86
+
87
+ def handle_returns_element(parse)
88
+ a = parse.node.attributes
89
+ @aggregate.returns = ResultType.new(
90
+ a['type-schema'],
91
+ a['type'],
92
+ a['capacity'],
93
+ a['precision'],
94
+ a['scale'],
95
+ )
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,92 @@
1
+ require 'mkxms/mssql/utils'
2
+
3
+ module Mkxms; end
4
+
5
+ module Mkxms::Mssql
6
+ class ClrAssembly
7
+ include ExtendedProperties
8
+
9
+ RaiserrorSource = Utils::RaiserrorWriter.new("%s: Missing or misconfigured assembly %s")
10
+
11
+ def initialize(name, lib_name = "", access:, owner: nil)
12
+ @name = name
13
+ @error_stmt = RaiserrorSource.next_statement("ERROR".sql_quoted, name.sql_quoted, severity: :error)
14
+ @warning_stmt = RaiserrorSource.next_statement("WARNING".sql_quoted, name.sql_quoted, severity: :warning)
15
+ @lib_name = lib_name
16
+ @access = access
17
+ @owner = owner
18
+ end
19
+
20
+ attr_reader :name, :error_stmt, :warning_stmt
21
+ attr_accessor :lib_name, :owner, :access
22
+
23
+ def self.setup_sql
24
+ [].tap do |s|
25
+ s << "IF NOT EXISTS (SELECT * FROM sys.tables t WHERE t.object_id = OBJECT_ID(N'xmigra.ignored_clr_assemblies'))"
26
+ s << " CREATE TABLE xmigra.ignored_clr_assemblies (name SYSNAME PRIMARY KEY);"
27
+
28
+ s << "" # Give a newline at the end
29
+ end.join("\n")
30
+ end
31
+
32
+ def to_sql
33
+ [].tap do |s|
34
+ s << "IF NOT EXISTS ("
35
+ s << " SELECT asm.name"
36
+ s << " FROM sys.assemblies asm"
37
+ s << " WHERE asm.is_visible = 1"
38
+ s << " AND QUOTENAME(asm.name) = #{name.sql_quoted}"
39
+ s << " UNION ALL"
40
+ s << " SELECT asm.name"
41
+ s << " FROM xmigra.ignored_clr_assemblies asm"
42
+ s << " WHERE asm.name = #{name.sql_quoted}"
43
+ s << ") #{error_stmt};"
44
+
45
+ s << "IF NOT EXISTS ("
46
+ s << " SELECT asm.name, QUOTENAME(owner.name) as owner, REPLACE(LOWER(asm.permission_set_desc), '_', '-') as permission_set, asm.clr_name as library"
47
+ s << " FROM sys.assemblies asm"
48
+ s << " JOIN sys.database_principals owner ON asm.principal_id = owner.principal_id" if owner
49
+ s << " WHERE asm.is_visible = 1"
50
+ s << " AND QUOTENAME(asm.name) = #{name.sql_quoted}"
51
+ s << " -- #{warning_stmt.error_marker} Run the query up to this point for assembly configuration --"
52
+ cols = [
53
+ ["owner", owner],
54
+ ["permission_set", access],
55
+ ["library", lib_name],
56
+ ].map {|t, v| [t.ljust(v.length), v.ljust(t.length)]}
57
+ s << (" -- " + cols.map {|e| e[0]}.join(' ') + ' --')
58
+ s << (" -- Expected values: " + cols.map {|e| e[1]}.join(' ') + ' --')
59
+ s << " AND QUOTENAME(owner.name) = #{owner.sql_quoted}" if owner
60
+ s << " AND REPLACE(LOWER(asm.permission_set_desc), '_', '-') = #{access.sql_quoted}"
61
+ s << " AND asm.clr_name = #{lib_name.sql_quoted}"
62
+ s << " UNION ALL"
63
+ s << " SELECT asm.name, NULL, NULL, NULL"
64
+ s << " FROM xmigra.ignored_clr_assemblies asm"
65
+ s << " WHERE asm.name = #{name.sql_quoted}"
66
+ s << ") #{warning_stmt};"
67
+
68
+ s << "" # Gives a newline at the end
69
+ end.join("\n")
70
+ end
71
+ end
72
+
73
+ class ClrAssemblyHandler
74
+ include PropertyHandler::ElementHandler
75
+
76
+ def initialize(assemblies, node)
77
+ a = node.attributes
78
+
79
+ @assembly = ClrAssembly.new(
80
+ a['name'],
81
+ owner: a['owner'],
82
+ access: a['permission-set']
83
+ ).tap do |asm|
84
+ assemblies << asm
85
+ end
86
+ end
87
+
88
+ def handle_text(content, parent_node)
89
+ @assembly.lib_name << content
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,172 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/clr_impl'
3
+ require 'mkxms/mssql/utils'
4
+
5
+ module Mkxms; end
6
+
7
+ module Mkxms::Mssql
8
+ class ClrFunction
9
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
10
+ include Utils::SchemaQualifiedName
11
+ extend Utils::InitializedAttributes
12
+
13
+ SQL_OBJECT_TYPE = 'FUNCTION'
14
+
15
+ class ResultTable
16
+ extend Utils::InitializedAttributes
17
+
18
+ attr_init(:columns) {[]}
19
+
20
+ class Column
21
+ include ExtendedProperties
22
+
23
+ SQL_OBJECT_TYPE = 'COLUMN'
24
+
25
+ def initialize(name, result_type)
26
+ @name = name
27
+ @result_type = result_type
28
+ end
29
+
30
+ attr_accessor :name, :result_type
31
+
32
+ def type_spec
33
+ result_type.type_spec
34
+ end
35
+ end
36
+ end
37
+
38
+ def initialize(attrs)
39
+ @schema = attrs['schema']
40
+ @name = attrs['name']
41
+ @execute_as = attrs['execute-as']
42
+ end
43
+
44
+ attr_accessor :schema, :name, :execute_as, :clr_impl, :returns, :result_table
45
+ attr_init(:params) {[]}
46
+
47
+ def to_sql
48
+ (procedure_def_sql + extended_properties_sql + param_properties_sql + result_column_properties_sql).join("\n")
49
+ end
50
+
51
+ def procedure_def_sql
52
+ [[].tap do |lines|
53
+ lines << "CREATE FUNCTION [{filename}] ("
54
+ lines << params.map do |param|
55
+ " #{param.name} #{param.type_spec}".tap do |param_spec|
56
+ param_spec << " = #{param.default_value}" if param.default_value
57
+ end
58
+ end.join(",\n")
59
+ lines << ")"
60
+ case
61
+ when returns
62
+ lines << "RETURNS #{returns.type_spec}"
63
+ when result_table
64
+ lines << "RETURNS TABLE ("
65
+ lines << result_table.columns.map do |col|
66
+ " #{col.name} #{col.type_spec}"
67
+ end.join(",\n")
68
+ lines << ")"
69
+ else
70
+ raise RuntimeError.new("Function return not defined")
71
+ end
72
+ case execute_as
73
+ when "OWNER"
74
+ lines << "WITH EXECUTE AS OWNER"
75
+ when String
76
+ lines << "WITH EXECUTE AS '#{Utils.unquoted_name execute_as}'"
77
+ end
78
+ lines << "AS EXTERNAL NAME #{clr_impl.full_specifier};"
79
+ end.join("\n")]
80
+ end
81
+
82
+ def param_properties_sql
83
+ params.map do |param|
84
+ subitem_extended_properties_sql(param)
85
+ end
86
+ end
87
+
88
+ def result_column_properties_sql
89
+ return [] unless result_table
90
+ result_table.columns.map do |col|
91
+ subitem_extended_properties_sql(col)
92
+ end
93
+ end
94
+ end
95
+
96
+ class ClrFunctionHandler
97
+ include PropertyHandler::ElementHandler
98
+
99
+ class ResultTableColumnHandler
100
+ include PropertyHandler::ElementHandler
101
+
102
+ def initialize(column)
103
+ @column = store_properties_on(column)
104
+ end
105
+ end
106
+
107
+ def initialize(functions, node)
108
+ a = node.attributes
109
+
110
+ @function = ClrFunction.new(a).tap do |f|
111
+ store_properties_on f
112
+ functions << f
113
+ end
114
+ end
115
+
116
+ def handle_implementation_element(parse)
117
+ a = parse.node.attributes
118
+ @function.clr_impl = ClrMethod.new(a['assembly'], a['class'], a['method'])
119
+ end
120
+
121
+ def handle_parameter_element(parse)
122
+ a = parse.node.attributes
123
+ Parameter.new(
124
+ a['name'],
125
+ a['type-schema'],
126
+ a['type'],
127
+ a['capacity'],
128
+ a['precision'],
129
+ a['scale'],
130
+ a['default'],
131
+ a['output'],
132
+ ).tap do |param|
133
+ @function.params << param
134
+ parse.context = ParameterHandler.new(param)
135
+ end
136
+ end
137
+
138
+ def handle_returns_element(parse)
139
+ a = parse.node.attributes
140
+ @function.returns = ResultType.new(
141
+ a['type-schema'],
142
+ a['type'],
143
+ a['capacity'],
144
+ a['precision'],
145
+ a['scale'],
146
+ )
147
+ end
148
+
149
+ def handle_result_table_element(parse)
150
+ @function.result_table = @result_table = ClrFunction::ResultTable.new
151
+ end
152
+
153
+ def handle_column_element(parse)
154
+ a = parse.node.attributes
155
+ ClrFunction::ResultTable::Column.new(
156
+ a['name'],
157
+ ResultType.new(
158
+ a['type-schema'],
159
+ a['type'],
160
+ a['capacity'],
161
+ a['precision'],
162
+ a['scale'],
163
+ a['collation'],
164
+ )
165
+ ).tap do |col|
166
+ @result_table.columns << col
167
+ # Dispatch parse for column properties
168
+ parse.context = ResultTableColumnHandler.new(col)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,58 @@
1
+ require 'mkxms/mssql/property_handler'
2
+
3
+ module Mkxms; end
4
+
5
+ module Mkxms::Mssql
6
+ ClrMethod = Struct.new(:assembly, :asm_class, :method) do
7
+ def full_specifier
8
+ to_a.join('.')
9
+ end
10
+ end
11
+
12
+ ClrClass = Struct.new(:assembly, :asm_class) do
13
+ def full_specifier
14
+ to_a.join('.')
15
+ end
16
+ end
17
+
18
+ # The Parameter class(es) are defined here because they are only important
19
+ # for CLR-linked objects
20
+ Parameter = Struct.new(
21
+ :name,
22
+ :type_schema, :type, :capacity, :precision, :scale,
23
+ :default_value,
24
+ :output
25
+ ) do
26
+ include ExtendedProperties
27
+
28
+ SQL_OBJECT_TYPE = 'PARAMETER'
29
+
30
+ def type_spec
31
+ [type_schema, type].compact.join(".").tap do |result|
32
+ result << "(#{capacity})" if capacity
33
+ result << "(#{[precision, scale].compact.join(', ')})" if precision
34
+ end
35
+ end
36
+ end
37
+
38
+ class ParameterHandler
39
+ include PropertyHandler::ElementHandler
40
+
41
+ def initialize(parameter)
42
+ @parameter = parameter
43
+ end
44
+
45
+ attr_reader :parameter
46
+ end
47
+
48
+ # Used for scalar and result table column type specification
49
+ ResultType = Struct.new(:schema, :name, :capacity, :precision, :scale, :collation) do
50
+ def type_spec
51
+ [schema, name].compact.join('.').tap do |result|
52
+ result << "(#{capacity})" if capacity
53
+ result << "(#{[precision, scale].compact.join(', ')})"
54
+ result << " COLLATE #{collation}" if collation
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,88 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/clr_impl'
3
+ require 'mkxms/mssql/utils'
4
+
5
+ module Mkxms; end
6
+
7
+ module Mkxms::Mssql
8
+ class ClrStoredProcedure
9
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
10
+ include Utils::SchemaQualifiedName
11
+ extend Utils::InitializedAttributes
12
+
13
+ SQL_OBJECT_TYPE = 'PROCEDURE'
14
+
15
+ def initialize(attrs)
16
+ @schema = attrs['schema']
17
+ @name = attrs['name']
18
+ @execute_as = attrs['execute-as']
19
+ end
20
+
21
+ attr_accessor :schema, :name, :clr_impl, :execute_as
22
+ attr_init(:params) {[]}
23
+
24
+ def to_sql
25
+ (procedure_def_sql + extended_properties_sql + param_properties_sql).join("\n")
26
+ end
27
+
28
+ def procedure_def_sql
29
+ [[].tap do |lines|
30
+ lines << "CREATE PROCEDURE [{filename}]"
31
+ lines << params.map do |param|
32
+ " #{param.name} #{param.type_spec}".tap do |param_spec|
33
+ param_spec << " = #{param.default_value}" if param.default_value
34
+ param_spec << " OUT" if param.output
35
+ end
36
+ end.join(",\n")
37
+ case execute_as
38
+ when "OWNER"
39
+ lines << "WITH EXECUTE AS OWNER"
40
+ when String
41
+ lines << "WITH EXECUTE AS '#{Utils.unquoted_name execute_as}'"
42
+ end
43
+ lines << "AS EXTERNAL NAME #{clr_impl.full_specifier};"
44
+ end.join("\n")]
45
+ end
46
+
47
+ def param_properties_sql
48
+ params.map do |param|
49
+ subitem_extended_properties_sql(param)
50
+ end.flatten
51
+ end
52
+ end
53
+
54
+ class ClrStoredProcedureHandler
55
+ include PropertyHandler::ElementHandler
56
+
57
+ def initialize(procedures, node)
58
+ a = node.attributes
59
+
60
+ @procedure = ClrStoredProcedure.new(a).tap do |sp|
61
+ store_properties_on sp
62
+ procedures << sp
63
+ end
64
+ end
65
+
66
+ def handle_implementation_element(parse)
67
+ a = parse.node.attributes
68
+ @procedure.clr_impl = ClrMethod.new(a['assembly'], a['class'], a['method'])
69
+ end
70
+
71
+ def handle_parameter_element(parse)
72
+ a = parse.node.attributes
73
+ Parameter.new(
74
+ a['name'],
75
+ a['type-schema'],
76
+ a['type'],
77
+ a['capacity'],
78
+ a['precision'],
79
+ a['scale'],
80
+ a['default'],
81
+ a['output'],
82
+ ).tap do |param|
83
+ @procedure.params << param
84
+ parse.context = ParameterHandler.new(param)
85
+ end
86
+ end
87
+ end
88
+ end