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,32 @@
1
+ require 'mkxms/mssql/keylike_constraint_helper'
2
+
3
+ module Mkxms; end
4
+
5
+ module Mkxms::Mssql
6
+ class UniqueConstraint < KeylikeConstraint
7
+ SQL_CONSTRAINT_TYPE = 'UNIQUE'
8
+
9
+ def sql_constraint_type
10
+ SQL_CONSTRAINT_TYPE
11
+ end
12
+ end
13
+
14
+ class UniqueConstraintHandler
15
+ def initialize(constraints, node)
16
+ a = node.attributes
17
+
18
+ @uconst = UniqueConstraint.new(a).tap do |c|
19
+ constraints << c
20
+ end
21
+ end
22
+
23
+ def handle_column_element(parse)
24
+ a = parse.node.attributes
25
+
26
+ raise UnsupportedFeatureError.new("Unique constraints may not specify included columns (#{@uconst.qualified_table})") if a['included']
27
+ @uconst.columns << IndexColumn.new(a['name'], a['desc'] ? :descending : :ascending)
28
+ end
29
+
30
+ # TODO: Handle partitioned unique constraints
31
+ end
32
+ end
@@ -0,0 +1,83 @@
1
+ require 'tsort'
2
+
3
+ module Mkxms; end
4
+ module Mkxms::Mssql; end
5
+
6
+ module Mkxms::Mssql::Utils
7
+ INVALID_NAME_CHAR = /[^A-Za-z0-9_]/
8
+
9
+ module FlagsQueries
10
+ def flags_query(*syms, flags: :flags)
11
+ syms.each do |sym|
12
+ define_method((sym.to_s + '?').to_sym) {send(flags).include? sym}
13
+ end
14
+ end
15
+ end
16
+
17
+ module InitializedAttributes
18
+ def attr_init(*syms, &blk)
19
+ raise "No block given for initialization of attr_init" unless blk
20
+
21
+ syms.each do |sym|
22
+ inst_var = "@#{sym}".to_sym
23
+ define_method(sym) do
24
+ instance_variable_get(inst_var) ||
25
+ instance_variable_set(inst_var, blk[])
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module SchemaQualifiedName
32
+ def qualified_name
33
+ [schema, name].join('.')
34
+ end
35
+ end
36
+
37
+ class NameRefGraph
38
+ include TSort
39
+
40
+ def initialize(items, children: :children)
41
+ @items = items
42
+ @children_message = children
43
+ end
44
+
45
+ def tsort_each_node(&blk)
46
+ @items.each(&blk)
47
+ end
48
+
49
+ def tsort_each_child(item, &blk)
50
+ item.send(@children_message).each(&blk)
51
+ end
52
+ end
53
+ end
54
+
55
+ class << Mkxms::Mssql::Utils
56
+ def code_sym_for(s)
57
+ s.gsub(self::INVALID_NAME_CHAR, '_').downcase.to_sym
58
+ end
59
+
60
+ def unquoted_name(s)
61
+ return s unless s[0] == '[' && s[-1] == ']'
62
+ return s[1...-1].gsub(']]', ']')
63
+ end
64
+
65
+ def newline_prefixed(s)
66
+ "\n" + s
67
+ end
68
+
69
+ def chars_to_tab(prev, tab_width: 4)
70
+ (prev.chars.length + 3) % 4 + 1
71
+ end
72
+
73
+ def expand_tabs(s, tab_width: 4)
74
+ return s unless s.include? "\t"
75
+
76
+ s.each_line.map do |l|
77
+ while l.include? "\t"
78
+ l.sub!("\t") {|m| ' ' * chars_to_tab($`, tab_width: tab_width)}
79
+ end
80
+ l
81
+ end.join('')
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ module Mkxms
2
+ module Mssql
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,58 @@
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 View
9
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
10
+ include Utils::SchemaQualifiedName
11
+
12
+ SQL_OBJECT_TYPE = 'VIEW'
13
+
14
+ def initialize(attrs)
15
+ @schema = attrs['schema']
16
+ @name = attrs['name']
17
+ @definition = ''
18
+ @references = []
19
+ end
20
+
21
+ attr_accessor :schema, :name
22
+ attr_reader :definition, :references
23
+
24
+ def to_sql
25
+ mvdef = AccessObjectDefinition.replace_object_name(definition, "[{filename}]")
26
+ ([mvdef] + extended_properties_sql).join("\n")
27
+ end
28
+ end
29
+
30
+ class ViewHandler
31
+ include PropertyHandler::ElementHandler
32
+
33
+ def initialize(views, node)
34
+ a = node.attributes
35
+
36
+ @view = View.new(a).tap do |v|
37
+ views << v
38
+ end
39
+ end
40
+
41
+ def extended_properties
42
+ @view.extended_properties
43
+ end
44
+
45
+ def handle_definition_element(parse); end
46
+
47
+ def handle_references_element(parse)
48
+ @view.references << %w[schema name].map {|k| parse.node.attributes[k]}.join('.')
49
+ end
50
+
51
+ def handle_text(text, parent_element)
52
+ case [parent_element.namespace, parent_element.name]
53
+ when ['', 'definition']
54
+ @view.definition << text
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ require "optparse"
2
+ require "ostruct"
3
+ require "pathname"
4
+ require "rexml/document"
5
+
6
+ module Mkxms
7
+ module Mssql
8
+ def self.parse_argv(argv = ARGV.dup)
9
+ options = OpenStruct.new
10
+ optparser = OptionParser.new do |flags|
11
+ flags.banner = "Usage: #{File.basename($0)} [<option> [<option> ...]] [DB_DESCRIPTION_FILE]"
12
+ flags.separator ''
13
+ flags.separator 'Options:'
14
+
15
+ options.schema_dir = Pathname.pwd
16
+ flags.on('-o', '--outdir=SCHEMA_DIR', "Output in SCHEMA_DIR") do |schema_dir|
17
+ options.schema_dir = Pathname(schema_dir).expand_path
18
+ end
19
+ end
20
+
21
+ db_files = optparser.parse(argv)
22
+ case db_files.length
23
+ when ->(n) {n > 1}
24
+ "Too many DB_DESCRIPTION_FILEs given"
25
+ end.tap {|msg| raise ProgramArgumentError.new(msg) if msg}
26
+
27
+ return [db_files[0], options]
28
+ end
29
+
30
+ def self.generate_from(document, options)
31
+ db_handler = DatabaseHandler.new(**(options.to_h))
32
+ engine = Engine.new(document, db_handler)
33
+ engine.run
34
+ db_handler.create_source_files
35
+ end
36
+
37
+ def self.with_db_description_io(file_path, &blk)
38
+ if file_path
39
+ File.open(file_path, 'r', &blk)
40
+ else
41
+ blk.call($stdin)
42
+ end
43
+ end
44
+
45
+ def self.run_program(argv = ARGV.dup)
46
+ description_file, options = parse_argv(argv)
47
+ document = with_db_description_io(description_file) do |xml_file|
48
+ REXML::Document.new(xml_file)
49
+ end
50
+ generate_from(document, options)
51
+ end
52
+ end
53
+ end
54
+
55
+ require "mkxms/mssql/database_handler"
56
+ require "mkxms/mssql/engine"
57
+ require "mkxms/mssql/exceptions"
58
+ require "mkxms/mssql/version"
59
+
60
+ if __FILE__.eql? $0
61
+ Mkxms::Mssql.run_program
62
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mkxms/mssql/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mkxms-mssql"
8
+ spec.version = Mkxms::Mssql::VERSION
9
+ spec.authors = ["Richard Weeks"]
10
+ spec.email = ["rtweeks21@gmail.com"]
11
+ spec.summary = %q{XMigra source files from MS-SQL database description.}
12
+ spec.description = %q{Build a complete set of XMigra source files from an XML document (as produced by the mssql-eyewkas.sql script) describing an MS-SQL database.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "xmigra", '~> 1.1'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ end
@@ -0,0 +1,218 @@
1
+ require 'mkxms/mssql/indented_string_builder'
2
+
3
+ class RSpec::Expectations::ExpectationTarget
4
+ def to_have(length_expectation)
5
+ (match = Object.new).extend RSpec::Matchers
6
+ to match.have length_expectation
7
+ end
8
+
9
+ def to_not_have(length_expectation)
10
+ (match = Object.new).extend RSpec::Matchers
11
+ to_not match.have length_expectation
12
+ end
13
+ end
14
+
15
+ RSpec::Matchers.define :have do |expected|
16
+ match do |actual|
17
+ actual.length == expected.count
18
+ end
19
+ failure_message do |actual|
20
+ "expected #{actual} to have #{expected.count} items"
21
+ end
22
+ end
23
+
24
+ RSpec::Expectations::LengthExpectation = Struct.new(:count)
25
+ class Integer
26
+ def items
27
+ RSpec::Expectations::LengthExpectation.new(self)
28
+ end
29
+
30
+ def item
31
+ raise "Numericity mismatch" unless self == 1
32
+ RSpec::Expectations::LengthExpectation.new(1)
33
+ end
34
+ end
35
+
36
+ describe Mkxms::Mssql::IndentedStringBuilder do
37
+ def check_contractor_example(s)
38
+ lines = s.to_s.lines
39
+ expect(lines).to_have 4.items
40
+ ["SELECT", " name", " AND trade", ";"].each.with_index do |line_start, i|
41
+ expect(lines[i]).to start_with(line_start)
42
+ end
43
+ end
44
+
45
+ it "can print a single line like a regular StringIO" do
46
+ subject.puts("Hello world!")
47
+ expect(subject.to_s.lines).to_have 1.item
48
+ end
49
+
50
+ it "can substitute into a line" do
51
+ sub = "happy"
52
+ subject.puts("I'm feeling %s.") {sub}
53
+ expect(subject.to_s).to include(sub)
54
+ expect(subject.to_s.lines).to_have 1.item
55
+ end
56
+
57
+ it "can substitue a value that is not a string into a template" do
58
+ sub = 17
59
+ subject.puts("I would like %s candy bars.") {sub}
60
+ expect(subject.to_s).to include(sub.to_s)
61
+ expect(subject.to_s.lines).to_have 1.item
62
+ end
63
+
64
+ it "can substitute an indented section" do
65
+ conditions = "name = 'Joe'\nAND trade='plumber'"
66
+ subject.puts("SELECT * FROM contractors WHERE %s;", :each_indent=>' ') {conditions}
67
+ expect(subject.to_s.lines).to_have 4.items
68
+ check_contractor_example(subject)
69
+ end
70
+
71
+ it "can #puts without substitution" do
72
+ subject.puts("weird %stuff", :sub => nil)
73
+ expect(subject.to_s).to eql("weird %stuff\n")
74
+ end
75
+
76
+ context "begin/end template" do
77
+ let(:template) {"BEGIN".."END"}
78
+
79
+ it "substitutes a single line correctly" do
80
+ subject.puts(template) {"body"}
81
+ expect(subject.to_s).to eql("BEGIN\n body\nEND\n")
82
+ end
83
+
84
+ it "substitutes multiple lines correctly" do
85
+ subject.puts(template) {"body1\nbody2"}
86
+ expect(subject.to_s).to eql("BEGIN\n body1\n body2\nEND\n")
87
+ end
88
+ end
89
+
90
+ it "yields the matched name if the :sub option is :named" do
91
+ sub_indicators = []
92
+ subject.puts("Eeny {meeny} {miney} mo", :sub => :named) {|insert|
93
+ sub_indicators << insert
94
+ "woot!"
95
+ }
96
+ expect(sub_indicators).to include('meeny')
97
+ expect(sub_indicators).to include('miney')
98
+ end
99
+
100
+ context "DSL" do
101
+ it "reroutes #puts to the builder (not $stdout)" do
102
+ expect do
103
+ subject.dsl {
104
+ puts "Hello, world!"
105
+ }
106
+ end.not_to output.to_stdout
107
+ expect(subject.to_s).to eql("Hello, world!\n")
108
+ end
109
+
110
+ it "provides for indented blocks" do
111
+ subject.dsl {
112
+ puts "SELECT * FROM contractors WHERE"
113
+ indented {
114
+ puts "name = 'Joe'"
115
+ puts "AND trade = 'plumber'"
116
+ }
117
+ puts ";"
118
+ }
119
+ check_contractor_example(subject)
120
+ end
121
+
122
+ it "allows indented injection with #puts" do
123
+ subject.dsl {
124
+ puts "SELECT * FROM contractors WHERE %s;" do
125
+ puts "name = 'Joe'"
126
+ puts "AND trade = 'plumber'"
127
+ end
128
+ }
129
+ check_contractor_example(subject)
130
+ end
131
+
132
+ it "captures variables from surrounding binding" do
133
+ the_answer = 42
134
+ subject.dsl {
135
+ puts "The answer to the ultimate question of life, the universe, and everything is #{the_answer}."
136
+ }
137
+ expect(subject.to_s).to include(the_answer.to_s)
138
+ end
139
+
140
+ it "allows calls to methods defined in the surrounding binding" do
141
+ def blargize(s)
142
+ s + "blarg"
143
+ end
144
+
145
+ subject.dsl {
146
+ puts "All answers exist on the #{blargize 'web'}."
147
+ }
148
+ end
149
+
150
+ it "does not capture output to $stdout within methods of the surrounding binding" do
151
+ def real_puts(s)
152
+ puts s
153
+ end
154
+
155
+ expect {
156
+ subject.dsl {
157
+ real_puts "This goes out!"
158
+ }
159
+ }.to output.to_stdout
160
+ end
161
+
162
+ it "is available from the class and returns a string" do
163
+ class_dsl_result = subject.class.dsl {
164
+ puts "Hello, world!"
165
+ }
166
+
167
+ expect(class_dsl_result).to eql("Hello, world!\n")
168
+ end
169
+
170
+ context "in subclass" do
171
+ it "properly handles calls to methods nested within DSL" do
172
+ test_class = Class.new(subject.class) do
173
+ def initialize
174
+ super
175
+
176
+ dsl {
177
+ puts "BEGIN"
178
+ indented {
179
+ add_command
180
+ }
181
+ puts "END"
182
+ }
183
+ end
184
+
185
+ def add_command
186
+ dsl {
187
+ puts "command"
188
+ }
189
+ end
190
+ end
191
+
192
+ test_instance = test_class.new
193
+ expect(test_instance.to_s).to eql("BEGIN\n command\nEND\n")
194
+ end
195
+
196
+ it "property handles plain #puts calls within a called method nested within DSL" do
197
+ test_class = Class.new(subject.class) do
198
+ def initialize
199
+ super
200
+
201
+ dsl {
202
+ puts "BEGIN"
203
+ indented {add_command}
204
+ puts "END"
205
+ }
206
+ end
207
+
208
+ def add_command
209
+ puts "command"
210
+ end
211
+ end
212
+
213
+ test_instance = test_class.new
214
+ expect(test_instance.to_s).to eql("BEGIN\n command\nEND\n")
215
+ end
216
+ end
217
+ end
218
+ end