mkxms-mssql 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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