mkxms-mssql 1.0.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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +53 -0
  6. data/Rakefile +1 -0
  7. data/bin/mkxms-mssql +5 -0
  8. data/lib/mkxms/mssql/access_object_definition.rb +61 -0
  9. data/lib/mkxms/mssql/adoption_script_writer.rb +1486 -0
  10. data/lib/mkxms/mssql/check_constraint_handler.rb +56 -0
  11. data/lib/mkxms/mssql/database_handler.rb +339 -0
  12. data/lib/mkxms/mssql/default_constraint_handler.rb +46 -0
  13. data/lib/mkxms/mssql/engine.rb +88 -0
  14. data/lib/mkxms/mssql/exceptions.rb +10 -0
  15. data/lib/mkxms/mssql/filegroup_handler.rb +81 -0
  16. data/lib/mkxms/mssql/foreign_key_handler.rb +85 -0
  17. data/lib/mkxms/mssql/function_handler.rb +74 -0
  18. data/lib/mkxms/mssql/indented_string_builder.rb +199 -0
  19. data/lib/mkxms/mssql/index_column.rb +11 -0
  20. data/lib/mkxms/mssql/index_handler.rb +98 -0
  21. data/lib/mkxms/mssql/keylike_constraint_helper.rb +67 -0
  22. data/lib/mkxms/mssql/permission_handler.rb +115 -0
  23. data/lib/mkxms/mssql/primary_key_handler.rb +36 -0
  24. data/lib/mkxms/mssql/property_handler.rb +87 -0
  25. data/lib/mkxms/mssql/query_cursor.rb +111 -0
  26. data/lib/mkxms/mssql/role_handler.rb +55 -0
  27. data/lib/mkxms/mssql/schema_handler.rb +42 -0
  28. data/lib/mkxms/mssql/sql_string_manipulators.rb +46 -0
  29. data/lib/mkxms/mssql/statistics_handler.rb +59 -0
  30. data/lib/mkxms/mssql/stored_procedure_handler.rb +65 -0
  31. data/lib/mkxms/mssql/table_handler.rb +180 -0
  32. data/lib/mkxms/mssql/unique_constraint_handler.rb +32 -0
  33. data/lib/mkxms/mssql/utils.rb +83 -0
  34. data/lib/mkxms/mssql/version.rb +5 -0
  35. data/lib/mkxms/mssql/view_handler.rb +58 -0
  36. data/lib/mkxms/mssql.rb +62 -0
  37. data/mkxms-mssql.gemspec +26 -0
  38. data/spec/utils/indented_string_builder_spec.rb +218 -0
  39. data/spec/utils/query_cursor_spec.rb +57 -0
  40. metadata +142 -0
@@ -0,0 +1,10 @@
1
+
2
+ module Mkxms
3
+ module Mssql
4
+ class UnsupportedFeatureError < Exception
5
+ end
6
+
7
+ class ProgramArgumentError < Exception
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,81 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/utils'
3
+
4
+ module Mkxms; end
5
+
6
+ module Mkxms::Mssql
7
+ class Filegroup
8
+ include ExtendedProperties
9
+
10
+ def initialize(default: false, read_only: false)
11
+ @default = default
12
+ @read_only = read_only
13
+ end
14
+
15
+ def default?
16
+ return @default
17
+ end
18
+
19
+ def read_only?
20
+ return @read_only
21
+ end
22
+ end
23
+
24
+ class FilegroupHandler
25
+ include PropertyHandler::ElementHandler
26
+
27
+ def initialize(filegroups, node)
28
+ group_options = Hash[
29
+ %w[default read-only].map do |a|
30
+ [Utils.code_sym_for(a), node.attributes.has_key?(a)]
31
+ end
32
+ ]
33
+ @filegroup = Filegroup.new(**group_options).tap do |fg|
34
+ filegroups << fg
35
+ end
36
+ @files = []
37
+ end
38
+
39
+ def extended_properties
40
+ @filegroup.extended_properties
41
+ end
42
+
43
+ def handle_file_element(parse)
44
+ parse.context = DatabaseFile.new(@files, parse.node)
45
+ end
46
+ end
47
+
48
+ class DatabaseFile
49
+ include ExtendedProperties, PropertyHandler::ElementHandler
50
+
51
+ def initialize(files, node)
52
+ @properties = Hash[
53
+ node.attributes.each_pair.map do |k, v|
54
+ [Utils.code_sym_for(k), (k == v ? true : v)]
55
+ end
56
+ ]
57
+ end
58
+
59
+ def name
60
+ @properties[:name]
61
+ end
62
+
63
+ def offline?
64
+ @properties[:offline]
65
+ end
66
+
67
+ def max_size_kb
68
+ value = @properties[:max_size]
69
+ return :available_space if value == 'available'
70
+ return value.to_i
71
+ end
72
+
73
+ def growth
74
+ @properties[:growth].to_i
75
+ end
76
+
77
+ def grow_by_fraction?
78
+ @properties[:growth_units] == 'percent'
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,85 @@
1
+ require 'mkxms/mssql/property_handler'
2
+
3
+ module Mkxms; end
4
+
5
+ module Mkxms::Mssql
6
+ class ForeignKey
7
+ include ExtendedProperties, Property::Hosting
8
+
9
+ def self.generated_name
10
+ "XMigra_unnamed_foreign_key_constraint_#{@anon_counter = (@anon_counter || 0) + 1}"
11
+ end
12
+
13
+ def initialize(schema, table, name, on_delete: 'NO ACTION', on_update: 'NO ACTION', enabled: true)
14
+ @schema = schema
15
+ @table = table
16
+ @delete_reconciliation = on_delete
17
+ @update_reconciliation = on_update
18
+ @enabled = enabled
19
+ @name = name || self.class.generated_name
20
+ @is_unnamed = !name
21
+ @links = []
22
+ end
23
+
24
+ attr_accessor :schema, :table, :name
25
+ attr_accessor :delete_reconciliation, :update_reconciliation, :enabled
26
+ attr_accessor :references
27
+ attr_reader :links # Array of elements like [column_in_referrer, column_in_referent]
28
+
29
+ def to_sql
30
+ "ALTER TABLE #{qualified_table} ADD CONSTRAINT #@name FOREIGN KEY (%s) REFERENCES #{@references[0]}.#{@references[1]} (%s)%s%s;" % [
31
+ @links.map {|e| e[0]}.join(', '),
32
+ @links.map {|e| e[1]}.join(', '),
33
+ (" ON DELETE #@delete_reconciliation" if @delete_reconciliation),
34
+ (" ON UPDATE #@update_reconciliation" if @update_reconciliation),
35
+ ] + (
36
+ @enabled ? '' : "\nALTER TABLE #{qualified_table} NOCHECK CONSTRAINT #@name;"
37
+ ) + extended_properties_sql.joined_on_new_lines
38
+ end
39
+
40
+ def qualified_table
41
+ "#@schema.#@table"
42
+ end
43
+
44
+ def qualified_name
45
+ "#@schema.#@name" if @name
46
+ end
47
+
48
+ def property_subject_identifiers
49
+ ['SCHEMA', schema, 'TABLE', table, 'CONSTRAINT', name].map {|n| Utils.unquoted_name(n)}
50
+ end
51
+
52
+ def unnamed?
53
+ @is_unnamed
54
+ end
55
+ end
56
+
57
+ class ForeignKeyHandler
58
+ include PropertyHandler::ElementHandler
59
+
60
+ def initialize(constraints, node)
61
+ a = node.attributes
62
+
63
+ @relation = ForeignKey.new(
64
+ a['schema'], a['table'], a['name'],
65
+ on_delete: a['on-delete'],
66
+ on_update: a['on-update'],
67
+ enabled: !a['disabled']
68
+ ).tap do |k|
69
+ constraints << k
70
+ end
71
+ end
72
+
73
+ def handle_referent_element(parse)
74
+ a = parse.node.attributes
75
+
76
+ @relation.references = [a['schema'], a['name']]
77
+ end
78
+
79
+ def handle_link_element(parse)
80
+ a = parse.node.attributes
81
+
82
+ @relation.links << [a['from'], a['to']]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,74 @@
1
+ require 'mkxms/mssql/access_object_definition'
2
+ require 'mkxms/mssql/property_handler'
3
+ require 'mkxms/mssql/utils'
4
+
5
+ module Mkxms; end
6
+
7
+ module Mkxms::Mssql
8
+ class Function
9
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
10
+ include Utils::SchemaQualifiedName
11
+
12
+ SQL_OBJECT_TYPE = 'FUNCTION'
13
+
14
+ def initialize(attrs)
15
+ @schema = attrs['schema']
16
+ @name = attrs['name']
17
+ @definition = ''
18
+ @references = []
19
+ @param_properties = Hash.new {|h, k| h[k] = ''}
20
+ end
21
+
22
+ attr_accessor :schema, :name
23
+ attr_reader :definition, :references, :param_properties
24
+
25
+ def to_sql
26
+ mvdef = AccessObjectDefinition.replace_object_name(definition, "[{filename}]")
27
+ ([mvdef] + extended_properties_sql + param_properties_sql).join("\n")
28
+ end
29
+
30
+ def param_properties_sql
31
+ @param_properties.each_pair.map do |k, v|
32
+ Property.addition_sql(k[1], v, property_subject_identifiers + ['PARAMETER', Utils.unquoted_name(k[0])])
33
+ end
34
+ end
35
+
36
+ def qualified_name
37
+ "#@schema.#@name"
38
+ end
39
+ end
40
+
41
+ class FunctionHandler
42
+ include PropertyHandler::ElementHandler
43
+
44
+ def initialize(functions, node)
45
+ a = node.attributes
46
+
47
+ @function = Function.new(a).tap do |f|
48
+ functions << f
49
+ end
50
+ end
51
+
52
+ def extended_properties
53
+ @function.extended_properties
54
+ end
55
+
56
+ def handle_definition_element(parse); end
57
+
58
+ def handle_references_element(parse)
59
+ @function.references << %w[schema name].map {|k| parse.node.attributes[k]}.join('.')
60
+ end
61
+
62
+ def handle_param_property(parse); end
63
+
64
+ def handle_text(text, parent_element)
65
+ case [parent_element.namespace, parent_element.name]
66
+ when ['', 'definition']
67
+ @function.definition << text
68
+ when ['', 'param-property']
69
+ a = parent_element.attributes
70
+ @function.param_properties[[a['param'], a['property']]] << text
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,199 @@
1
+ require 'forwardable'
2
+ require 'stringio'
3
+
4
+ module Mkxms; end
5
+
6
+ module Mkxms::Mssql
7
+ class IndentedStringBuilder
8
+ NAMED_SUBSTITUTIONS = /\{(?<name>\S+)\}/
9
+
10
+ class LineAccumulator
11
+ def initialize(indent, &flush_to)
12
+ @indent = indent
13
+ @flush_to = flush_to
14
+ @value = @indent.dup
15
+ end
16
+
17
+ def flush
18
+ @flush_to[@value]
19
+ @value = @indent.dup
20
+ end
21
+
22
+ def any_acculumation?
23
+ @value != @indent
24
+ end
25
+
26
+ def <<(v)
27
+ @value << v
28
+ return self
29
+ end
30
+ end
31
+
32
+ class DSL
33
+ extend Forwardable
34
+
35
+ def self.for(builder, block)
36
+ new(builder, block.binding).instance_eval(&block)
37
+ end
38
+
39
+ def initialize(builder, outer_binding)
40
+ @builder = builder
41
+ while sb = surrounding_binding(outer_binding)
42
+ outer_binding = sb
43
+ end
44
+ @outer_binding = outer_binding
45
+ end
46
+
47
+ def_delegators :@builder, :indented
48
+
49
+ def puts(*args, &blk)
50
+ if blk
51
+ @builder.puts(*args) {IndentedStringBuilder.new.tap {|i| i.dsl(&blk)}}
52
+ else
53
+ @builder.puts(*args)
54
+ end
55
+ end
56
+
57
+ private
58
+ def method_missing(sym, *args, &blk)
59
+ if args.empty? and blk.nil?
60
+ @outer_binding.eval sym.to_s
61
+ else
62
+ @outer_binding.eval("method(:#{sym})").call(*args, &blk)
63
+ end
64
+ end
65
+
66
+ def surrounding_binding(b)
67
+ if (b_self = b.eval("self")).kind_of? DSL
68
+ b_self.instance_variable_get(:@outer_binding)
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize(options = {})
74
+ @io = StringIO.new
75
+ @indent = 0
76
+ @each_indent = options.fetch(:each_indent, " ")
77
+ end
78
+
79
+ def to_s
80
+ @io.string
81
+ end
82
+
83
+ def indented(n = 1)
84
+ prev_indent = @indent
85
+ @indent += n
86
+ begin
87
+ yield
88
+ ensure
89
+ @indent = prev_indent
90
+ end
91
+ end
92
+
93
+ def puts(s = "", options = {})
94
+ sub_pattern = options.fetch(:sub, '%s')
95
+ sub_pattern = NAMED_SUBSTITUTIONS if sub_pattern == :named
96
+ if sub_pattern.nil?
97
+ @io.puts(s)
98
+ return s.each_line.lazy.map {|l| l.chomp}
99
+ end
100
+
101
+ sub_pattern = Regexp.compile(Regexp.escape(sub_pattern)) unless sub_pattern.is_a? Regexp
102
+ sub_name_index = sub_pattern.named_captures.fetch('name', []).min
103
+ scan_pattern = Regexp.union(sub_pattern, /\n|$/)
104
+ current_indent = indent_string
105
+
106
+ i = Enumerator.new {|y| v = 0; loop {y.yield(v); v += 1}}
107
+ completed = 0
108
+
109
+ lines = Enumerator.new do |e|
110
+ if s.is_a? Range
111
+ e.yield(current_indent + s.begin)
112
+ body = yield
113
+ hanging_indent = current_indent + @each_indent
114
+ if body.is_a?(String) || !body.respond_to?(:each)
115
+ body = body.to_s.each_line
116
+ end
117
+ body.each {|l| e.yield(hanging_indent + l.chomp)}
118
+ e.yield(current_indent + s.end)
119
+ next
120
+ end
121
+
122
+ line = LineAccumulator.new(current_indent) {|v| e.yield v}
123
+ subbed_multiline = false
124
+ s.scan(scan_pattern) do |m|
125
+ chunk_range, completed = completed...$~.begin(0), $~.end(0)
126
+ chunk_empty = !chunk_range.cover?(chunk_range.begin)
127
+
128
+ case
129
+ when m == "\n"
130
+ (line << s[chunk_range]).flush if line.any_acculumation? || !chunk_empty
131
+ next
132
+ when m == "" && !chunk_empty
133
+ (line << s[chunk_range]).flush
134
+ next
135
+ when m == ""
136
+ line.flush if line.any_acculumation?
137
+ next
138
+ end
139
+
140
+ # Expect repl is a string or an enumerator of lines
141
+ prev_indent = @indent
142
+ @indent += 1
143
+ hanging_indent = indent_string
144
+ begin
145
+ yield_from_string = proc do |r|
146
+ s_chunk = s[chunk_range]
147
+ if r.include? "\n"
148
+ line << s_chunk if line.any_acculumation? || !s_chunk.match(/^\s*$/)
149
+ line.flush if line.any_acculumation?
150
+ r.each_line {|l| e.yield(hanging_indent + l.chomp)}
151
+ else
152
+ line << s_chunk << r
153
+ end
154
+ end
155
+
156
+ yield_args = []
157
+ yield_args << m[sub_name_index - 1] if sub_name_index
158
+ yield_args << i.next
159
+ case repl = yield(*yield_args)
160
+ when String
161
+ yield_from_string[repl]
162
+ when ->(v){v.respond_to? :each}
163
+ (line << s[chunk_range]).flush
164
+ repl.each {|l| e.yield(
165
+ hanging_indent +
166
+ l.chomp.gsub("\n", hanging_indent + "\n")
167
+ )}
168
+ else
169
+ yield_from_string[repl.to_s]
170
+ end
171
+ ensure
172
+ @indent = prev_indent
173
+ end
174
+ end
175
+ end
176
+
177
+ lines.tap {|e| e.dup.each {|l| @io.puts l}}
178
+ end
179
+
180
+ def dsl(&block)
181
+ DSL.for(self, block)
182
+ return self
183
+ end
184
+
185
+ def self.dsl(&block)
186
+ new.dsl(&block).to_s
187
+ end
188
+
189
+ def each
190
+ @io.string.each_line {|l| yield l.chomp}
191
+ end
192
+ include Enumerable
193
+
194
+ protected
195
+ def indent_string
196
+ @each_indent * @indent
197
+ end
198
+ end
199
+ end