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