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,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