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
@@ -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