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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/bin/mkxms-mssql +5 -0
- data/lib/mkxms/mssql/access_object_definition.rb +61 -0
- data/lib/mkxms/mssql/adoption_script_writer.rb +1486 -0
- data/lib/mkxms/mssql/check_constraint_handler.rb +56 -0
- data/lib/mkxms/mssql/database_handler.rb +339 -0
- data/lib/mkxms/mssql/default_constraint_handler.rb +46 -0
- data/lib/mkxms/mssql/engine.rb +88 -0
- data/lib/mkxms/mssql/exceptions.rb +10 -0
- data/lib/mkxms/mssql/filegroup_handler.rb +81 -0
- data/lib/mkxms/mssql/foreign_key_handler.rb +85 -0
- data/lib/mkxms/mssql/function_handler.rb +74 -0
- data/lib/mkxms/mssql/indented_string_builder.rb +199 -0
- data/lib/mkxms/mssql/index_column.rb +11 -0
- data/lib/mkxms/mssql/index_handler.rb +98 -0
- data/lib/mkxms/mssql/keylike_constraint_helper.rb +67 -0
- data/lib/mkxms/mssql/permission_handler.rb +115 -0
- data/lib/mkxms/mssql/primary_key_handler.rb +36 -0
- data/lib/mkxms/mssql/property_handler.rb +87 -0
- data/lib/mkxms/mssql/query_cursor.rb +111 -0
- data/lib/mkxms/mssql/role_handler.rb +55 -0
- data/lib/mkxms/mssql/schema_handler.rb +42 -0
- data/lib/mkxms/mssql/sql_string_manipulators.rb +46 -0
- data/lib/mkxms/mssql/statistics_handler.rb +59 -0
- data/lib/mkxms/mssql/stored_procedure_handler.rb +65 -0
- data/lib/mkxms/mssql/table_handler.rb +180 -0
- data/lib/mkxms/mssql/unique_constraint_handler.rb +32 -0
- data/lib/mkxms/mssql/utils.rb +83 -0
- data/lib/mkxms/mssql/version.rb +5 -0
- data/lib/mkxms/mssql/view_handler.rb +58 -0
- data/lib/mkxms/mssql.rb +62 -0
- data/mkxms-mssql.gemspec +26 -0
- data/spec/utils/indented_string_builder_spec.rb +218 -0
- data/spec/utils/query_cursor_spec.rb +57 -0
- metadata +142 -0
@@ -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
|