mkxms-mssql 1.0.0 → 1.1.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -10
  3. data/lib/mkxms/mssql.rb +18 -0
  4. data/lib/mkxms/mssql/adoption_script_writer.rb +759 -91
  5. data/lib/mkxms/mssql/clr_aggregate_handler.rb +98 -0
  6. data/lib/mkxms/mssql/clr_assembly_handler.rb +92 -0
  7. data/lib/mkxms/mssql/clr_function_handler.rb +172 -0
  8. data/lib/mkxms/mssql/clr_impl.rb +58 -0
  9. data/lib/mkxms/mssql/clr_stored_procedure_handler.rb +88 -0
  10. data/lib/mkxms/mssql/clr_type_handler.rb +92 -0
  11. data/lib/mkxms/mssql/database_handler.rb +124 -3
  12. data/lib/mkxms/mssql/declaratives_creator.rb +206 -0
  13. data/lib/mkxms/mssql/dml_trigger_handler.rb +107 -0
  14. data/lib/mkxms/mssql/filegroup_handler.rb +1 -4
  15. data/lib/mkxms/mssql/function_handler.rb +1 -4
  16. data/lib/mkxms/mssql/indented_string_builder.rb +8 -2
  17. data/lib/mkxms/mssql/index_handler.rb +1 -4
  18. data/lib/mkxms/mssql/keywords.rb +492 -0
  19. data/lib/mkxms/mssql/primary_key_handler.rb +1 -4
  20. data/lib/mkxms/mssql/property_handler.rb +8 -0
  21. data/lib/mkxms/mssql/query_cursor.rb +12 -4
  22. data/lib/mkxms/mssql/references_handler.rb +24 -0
  23. data/lib/mkxms/mssql/role_handler.rb +1 -4
  24. data/lib/mkxms/mssql/scalar_type_handler.rb +108 -0
  25. data/lib/mkxms/mssql/schema_handler.rb +1 -4
  26. data/lib/mkxms/mssql/sql_string_manipulators.rb +4 -4
  27. data/lib/mkxms/mssql/statistics_handler.rb +1 -4
  28. data/lib/mkxms/mssql/stored_procedure_handler.rb +1 -4
  29. data/lib/mkxms/mssql/synonym_handler.rb +40 -0
  30. data/lib/mkxms/mssql/table_handler.rb +2 -8
  31. data/lib/mkxms/mssql/table_type_handler.rb +254 -0
  32. data/lib/mkxms/mssql/utils.rb +96 -0
  33. data/lib/mkxms/mssql/version.rb +1 -1
  34. data/lib/mkxms/mssql/view_handler.rb +1 -4
  35. data/spec/utils/indented_string_builder_spec.rb +21 -0
  36. data/spec/utils/query_cursor_spec.rb +2 -2
  37. data/spec/utils/sql_string_manipulators_spec.rb +59 -0
  38. metadata +18 -3
@@ -16,14 +16,11 @@ module Mkxms::Mssql
16
16
  a = node.attributes
17
17
 
18
18
  @pkey = PrimaryKey.new(a).tap do |c|
19
+ store_properties_on c
19
20
  constraints << c
20
21
  end
21
22
  end
22
23
 
23
- def extended_properties
24
- @pkey.extended_properties
25
- end
26
-
27
24
  def handle_column_element(parse)
28
25
  a = parse.node.attributes
29
26
 
@@ -54,6 +54,14 @@ module Mkxms::Mssql
54
54
  def handle_property_element(parse)
55
55
  parse.context = PropertyHandler.new(self, parse.node.attributes)
56
56
  end
57
+
58
+ private
59
+ def store_properties_on(target)
60
+ define_singleton_method(:extended_properties) do
61
+ target.extended_properties
62
+ end
63
+ return target
64
+ end
57
65
  end
58
66
 
59
67
  def initialize(describable, attrs)
@@ -4,9 +4,10 @@ module Mkxms::Mssql
4
4
  class QueryCursor
5
5
  def initialize(select_statement, variables, options = {})
6
6
  @select_statement = select_statement
7
- @select_statement += ';' unless @select_statement.end_with? ';'
7
+ @select_statement += ';' unless @select_statement =~ /;\s*\Z/
8
8
  @cursor = options[:cursor_name] || self.class.generated_cursor_name
9
9
  @out = options[:output_to] || $stdout
10
+ @indented = @out.respond_to?(:indented) ? @out.method(:indented) : ->(&blk) {blk.call}
10
11
  @global = options[:global]
11
12
  @indent = options[:indent] || ' '
12
13
 
@@ -54,7 +55,7 @@ module Mkxms::Mssql
54
55
  fetch_next
55
56
  @out.puts "IF @@FETCH_STATUS = 0"
56
57
  @out.puts "BEGIN"
57
- extra_action.call
58
+ indented {extra_action.call}
58
59
  @out.puts "END;"
59
60
  end
60
61
 
@@ -64,12 +65,15 @@ module Mkxms::Mssql
64
65
  def test_entry(opts = {})
65
66
  opts = {} unless opts.kind_of? Hash
66
67
  missing_action = expectation_failure_action(opts[:on_missing]) || proc {}
68
+ @out.puts
67
69
  fetch_next
68
70
  @out.puts "IF @@FETCH_STATUS <> 0"
69
71
  @out.puts "BEGIN"
70
- missing_action.call
72
+ indented {missing_action.call}
71
73
  @out.puts "END ELSE BEGIN"
72
- yield
74
+ indented {
75
+ yield
76
+ }
73
77
  @out.puts "END;"
74
78
  end
75
79
 
@@ -103,6 +107,10 @@ module Mkxms::Mssql
103
107
  @out.puts "CLOSE #{cursor_scope(false)} #@cursor; DEALLOCATE #{cursor_scope(false)} #@cursor;"
104
108
  end
105
109
 
110
+ def indented(&blk)
111
+ @indented.call(&blk)
112
+ end
113
+
106
114
  def self.generated_cursor_name
107
115
  @gensym_number ||= 0
108
116
  "gensym_cursor_#{@gensym_number += 1}"
@@ -0,0 +1,24 @@
1
+ require 'mkxms/mssql/utils'
2
+
3
+ module Mkxms; end
4
+
5
+ module Mkxms::Mssql
6
+ Reference = Struct.new(:schema, :name) do
7
+ include Utils::SchemaQualifiedName
8
+ end
9
+
10
+ module Dependencies
11
+ def dependencies
12
+ @dependencies ||= []
13
+ end
14
+ end
15
+
16
+ class ReferencesHandler
17
+ module ElementHandler
18
+ def handle_references_element(parse)
19
+ a = parse.node.attributes
20
+ referrer.dependencies << Reference.new(a['schema'], a['name'])
21
+ end
22
+ end
23
+ end
24
+ end
@@ -40,14 +40,11 @@ module Mkxms::Mssql
40
40
 
41
41
  def initialize(roles, node)
42
42
  @role = Role.new(node.attributes['name'], owner: node.attributes['owner']).tap do |r|
43
+ store_properties_on r
43
44
  roles << r
44
45
  end
45
46
  end
46
47
 
47
- def extended_properties
48
- @role.extended_properties
49
- end
50
-
51
48
  def handle_member_of_element(parse)
52
49
  @role.encompassing_roles << parse.node.attributes['name']
53
50
  end
@@ -0,0 +1,108 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/utils'
3
+
4
+ module Mkxms; end
5
+
6
+ module Mkxms::Mssql
7
+ class ScalarType
8
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
9
+ include Utils::SchemaQualifiedName
10
+
11
+ SQL_OBJECT_TYPE = 'TYPE'
12
+
13
+ def initialize(attrs)
14
+ a = attrs
15
+ @schema = a['schema']
16
+ @name = a['name']
17
+ @base_type = a['base-type']
18
+ @capacity = a['capacity']
19
+ @capacity = @capacity.to_i unless @capacity.nil? || @capacity == 'max'
20
+ @precision = a['precision']
21
+ @scale = a['scale']
22
+ @nullable = !!a['nullable']
23
+ end
24
+
25
+ attr_accessor :schema, :name, :base_type, :capacity, :precision, :scale, :default
26
+
27
+ def nullable?
28
+ @nullable
29
+ end
30
+
31
+ def nullable=(val)
32
+ @nullable = !!val
33
+ end
34
+
35
+ def type_spec
36
+ base_type.dup.tap do |ts|
37
+ case
38
+ when capacity
39
+ ts << "(#{capacity})"
40
+ when precision
41
+ ts << "(#{[precision, scale].compact.join(", ")})"
42
+ end
43
+ ts << " NOT NULL" unless nullable?
44
+ end
45
+ end
46
+
47
+ def to_sql
48
+ [].tap do |lines|
49
+ lines << "CREATE TYPE #{qualified_name}"
50
+ lines << "FROM #{type_spec};"
51
+
52
+ if default
53
+ lines << default.to_sql
54
+ lines << "EXEC sp_bindefault #{default.qualified_name.sql_quoted}, #{qualified_name.sql_quoted};"
55
+ end
56
+ end.join("\n")
57
+ end
58
+
59
+ def element_size
60
+ if %w[nchar nvarchar]
61
+ 2
62
+ else
63
+ 1
64
+ end
65
+ end
66
+ end
67
+
68
+ class Default
69
+ include Utils::SchemaQualifiedName
70
+
71
+ def initialize(attrs)
72
+ a = attrs
73
+ @schema = a['schema']
74
+ @name = a['name']
75
+ @definition = ""
76
+ end
77
+
78
+ attr_accessor :schema, :name
79
+ attr_reader :definition
80
+
81
+ def to_sql
82
+ "CREATE DEFAULT #{qualified_name} AS #{definition};"
83
+ end
84
+ end
85
+
86
+ class ScalarTypeHandler
87
+ include PropertyHandler::ElementHandler
88
+
89
+ def initialize(user_types, node)
90
+ a = node.attributes
91
+ ScalarType.new(a).tap do |t|
92
+ store_properties_on t
93
+ user_types << (@type = t)
94
+ end
95
+ end
96
+
97
+ def handle_default_element(parse)
98
+ @type.default = Default.new(parse.node.attributes)
99
+ end
100
+
101
+ def handle_text(text, parent_element)
102
+ case [parent_element.namespace, parent_element.name]
103
+ when ['', 'default']
104
+ @type.default.definition << text
105
+ end
106
+ end
107
+ end
108
+ end
@@ -31,12 +31,9 @@ module Mkxms::Mssql
31
31
 
32
32
  def initialize(schemas, node)
33
33
  @schema = Schema.new(node.attributes['name'], owner: node.attributes['owner']).tap do |s|
34
+ store_properties_on s
34
35
  schemas << s
35
36
  end
36
37
  end
37
-
38
- def extended_properties
39
- @schema.extended_properties
40
- end
41
38
  end
42
39
  end
@@ -13,13 +13,13 @@ module Mkxms::Mssql
13
13
  when margin.nil? && l =~ /^ *$/
14
14
  l
15
15
  when margin.nil?
16
- margin = /^ */.match(l).length
17
- l[margin..-1]
18
- when s =~/^\s*$/
16
+ margin = /^ */.match(l)[0].length
19
17
  l[margin..-1]
20
18
  else
21
- /^(?: *)(.*)/.match(l)[1]
19
+ /^(?: {0,#{margin}})(.*)/m.match(l)[1]
22
20
  end
21
+ end.tap do |lines|
22
+ lines.shift if lines.first == "\n"
23
23
  end.join('')
24
24
  end
25
25
 
@@ -44,14 +44,11 @@ module Mkxms::Mssql
44
44
  a = node.attributes
45
45
 
46
46
  @statistics = Statistics.new(a).tap do |s|
47
+ store_properties_on s
47
48
  statistics_objs << s
48
49
  end
49
50
  end
50
51
 
51
- def extended_properties
52
- @statistics.extended_properties
53
- end
54
-
55
52
  def handle_column_element(parse)
56
53
  @statistics.columns << parse.node.attributes['name']
57
54
  end
@@ -38,14 +38,11 @@ module Mkxms::Mssql
38
38
  a = node.attributes
39
39
 
40
40
  @procedure = StoredProcedure.new(a).tap do |sp|
41
+ store_properties_on sp
41
42
  procedures << sp
42
43
  end
43
44
  end
44
45
 
45
- def extended_properties
46
- @procedure.extended_properties
47
- end
48
-
49
46
  def handle_definition_element(parse); end
50
47
 
51
48
  def handle_references_element(parse); end
@@ -0,0 +1,40 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/utils'
3
+
4
+ module Mkxms; end
5
+
6
+ module Mkxms::Mssql
7
+ class Synonym
8
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
9
+ include Utils::SchemaQualifiedName
10
+
11
+ SQL_OBJECT_TYPE = 'SYNONYM'
12
+
13
+ def initialize(schema, name, referent)
14
+ @schema = schema
15
+ @name = name
16
+ @referent = referent
17
+ end
18
+
19
+ attr_accessor :schema, :name, :referent
20
+
21
+ def to_sql
22
+ [].tap do |lines|
23
+ lines << "CREATE SYNONYM #{qualified_name} FOR #{referent};"
24
+ lines.concat extended_properties_sql
25
+ end.join("\n")
26
+ end
27
+ end
28
+
29
+ class SynonymHandler
30
+ include PropertyHandler::ElementHandler
31
+
32
+ def initialize(synonyms, node)
33
+ a = node.attributes
34
+ Synonym.new(a['schema'], a['name'], a['for']).tap do |syn|
35
+ store_properties_on syn
36
+ synonyms << (@synonym = syn)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -128,16 +128,13 @@ module Mkxms::Mssql
128
128
  c.flags << :replicated if a['replicated']
129
129
  c.flags << :filestream if a['filestream']
130
130
  c.type_info.update(col_attrs)
131
+ store_properties_on c
131
132
  columns << c
132
133
  end
133
134
  end
134
135
 
135
136
  attr_reader :column
136
137
 
137
- def extended_properties
138
- @column.extended_properties
139
- end
140
-
141
138
  def handle_computed_expression_element(parse)
142
139
  column.flags << :persisted if parse.node.attributes['persisted']
143
140
  # Handle expression in #handle_text
@@ -156,6 +153,7 @@ module Mkxms::Mssql
156
153
  def initialize(tables, node)
157
154
  a = node.attributes
158
155
  @table = Table.new(a['schema'], a['name']).tap do |t|
156
+ store_properties_on t
159
157
  tables << t
160
158
  end
161
159
  @table.owner = a['owner']
@@ -165,10 +163,6 @@ module Mkxms::Mssql
165
163
  @rowguid_column = a['rowguidcol']
166
164
  end
167
165
 
168
- def extended_properties
169
- @table.extended_properties
170
- end
171
-
172
166
  def handle_column_element(parse)
173
167
  parse.context = ColumnHandler.new(@table.columns, parse.node)
174
168
  column = parse.context.column
@@ -0,0 +1,254 @@
1
+ require 'mkxms/mssql/property_handler'
2
+ require 'mkxms/mssql/utils'
3
+
4
+ module Mkxms; end
5
+
6
+ module Mkxms::Mssql
7
+ class TableType
8
+ class Column
9
+ include ExtendedProperties
10
+ extend Utils::InitializedAttributes
11
+
12
+ SQL_OBJECT_TYPE = 'COLUMN'
13
+
14
+ def initialize(attrs)
15
+ a = attrs
16
+ @name = a['name']
17
+ @type_schema = a['type-schema']
18
+ @type_name = a['type']
19
+ @capacity = a['capacity']
20
+ @capacity = @capacity.to_i unless @capacity.nil? || @capacity == 'max'
21
+ @precision = a['precision']
22
+ @scale = a['scale']
23
+ @collation = a['collation']
24
+ @nullable = !!a['nullable']
25
+ @ansi_padded = !a['not-ansi-padded']
26
+ @full_xml_document = !!a['full-xml-document']
27
+ @xml_schema_collection = a['xml_collection_id']
28
+ end
29
+
30
+ attr_accessor :name, :type_schema, :type_name, :capacity, :precision, :scale, :collation, :xml_schema_collection
31
+ attr_accessor :computed_expression
32
+ attr_init(:check_constraints) {[]}
33
+
34
+ def nullable?; @nullable; end
35
+ def nullable=(val); @nullable = !!val; end
36
+
37
+ def ansi_padded?; @ansi_padded; end
38
+ def ansi_padded=(val); @ansi_padded = !!val; end
39
+
40
+ def full_xml_document?; @full_xml_document; end
41
+ def full_xml_document=(val); @full_xml_document = !!val; end
42
+
43
+ def type_spec
44
+ [type_schema, type_name].compact.join('.').tap do |result|
45
+ result << "(#{capacity})" if capacity
46
+ result << " COLLATE #{collation}" if collation
47
+ result << "(#{[precision, scale].compact.join(', ')})" if precision
48
+ result << ' NOT NULL' unless nullable?
49
+ check_constraints.each do |c|
50
+ result << " #{c.to_sql}"
51
+ end
52
+ end
53
+ end
54
+
55
+ def max_byte_consumption
56
+ if [nil, '[sys]'].include?(type_schema) && %w[[nchar] [nvarchar]].include?(type_name)
57
+ 2 * capacity
58
+ else
59
+ capacity
60
+ end
61
+ end
62
+ end
63
+
64
+ class ConstraintColumn
65
+ def initialize(attrs)
66
+ @name = attrs['name']
67
+ @ascending = !attrs['desc']
68
+ end
69
+
70
+ attr_accessor :name
71
+
72
+ def ascending?; @ascending; end
73
+ def descending?; !@ascending; end
74
+ def ascending=(val); @ascending = !!val; end
75
+ def descending=(val); @ascending = !val; end
76
+
77
+ def spec
78
+ "#{name} #{ascending? ? "ASC" : "DESC"}"
79
+ end
80
+ end
81
+
82
+ class KeyConstraint
83
+ include ExtendedProperties
84
+ extend Utils::InitializedAttributes
85
+
86
+ SQL_OBJECT_TYPE = 'CONSTRAINT'
87
+
88
+ def initialize(attrs)
89
+ @type = attrs['type']
90
+ @clustered = !!attrs['clustered']
91
+ @ignore_duplicates = !!attrs['ignore-duplicates']
92
+ end
93
+
94
+ attr_accessor :type, :ignore_duplicates
95
+ attr_init(:columns) {[]}
96
+
97
+ def clustered?; @clustered; end
98
+ def clustered=(val); @clustered = !!val; end
99
+
100
+ def ignore_duplicates?; @ignore_duplicates; end
101
+ def ignore_duplicates=(val); @ignore_duplicates = !!val; end
102
+
103
+ def to_sql
104
+ "#{type} #{clustered? ? "CLUSTERED" : "NONCLUSTERED"} (#{
105
+ columns.map(&:spec).join(', ')
106
+ })".tap do |result|
107
+ result << " WITH (IGNORE_DUP_KEY = ON)" if ignore_duplicates?
108
+ end
109
+ end
110
+ end
111
+
112
+ class CheckConstraint
113
+ include ExtendedProperties
114
+ extend Utils::InitializedAttributes
115
+
116
+ SQL_OBJECT_TYPE = 'CONSTRAINT'
117
+
118
+ def initialize(attrs)
119
+ end
120
+
121
+ attr_init(:expression) {''}
122
+
123
+ def type
124
+ "CHECK"
125
+ end
126
+
127
+ def to_sql
128
+ "CHECK #{expression}"
129
+ end
130
+ end
131
+
132
+ include ExtendedProperties, Property::Hosting, Property::SchemaScoped
133
+ include Utils::SchemaQualifiedName
134
+ extend Utils::InitializedAttributes
135
+
136
+ SQL_OBJECT_TYPE = 'TYPE'
137
+
138
+ def initialize(attrs)
139
+ a = attrs
140
+ info_ver = (a['eyewkas_ver'] || 1.0).to_f
141
+ raise "mssql-eyewkas table-type ver. 1.1 or compatible required" if info_ver < 1.1 || info_ver >= 2
142
+ @schema = a['schema']
143
+ @name = a['name']
144
+ end
145
+
146
+ attr_accessor :schema, :name
147
+ attr_init(:columns, :constraints) {[]}
148
+
149
+ def to_sql
150
+ [].tap do |lines|
151
+ lines << "CREATE TYPE #{qualified_name} AS TABLE ("
152
+ columns.each_with_index do |col, i|
153
+ lines << " #{i == 0 ? " " : ","} #{col.name} #{col.type_spec}"
154
+ end
155
+ constraints.each do |c|
156
+ lines << " , #{c.to_sql}"
157
+ end
158
+ lines << ");"
159
+ lines << extended_properties_sql
160
+ columns.each do |col|
161
+ lines << subitem_extended_properties_sql(col)
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ class TableTypeColumnHandler
168
+ include PropertyHandler::ElementHandler
169
+
170
+ def initialize(column)
171
+ store_properties_on(@column = column)
172
+ end
173
+
174
+ def handle_computed_expression_element(parse)
175
+ # Do nothing
176
+ end
177
+
178
+ def handle_check_constraint_element(parse)
179
+ parse.delegate_to TableTypeCheckConstraintHandler, @column.check_constraints
180
+ end
181
+
182
+ def handle_text(text, parent_element)
183
+ case [parent_element.namespace, parent_element.name]
184
+ when ['', 'computed-expression']
185
+ (@column.computed_expression ||= '') << text
186
+ end
187
+ end
188
+ end
189
+
190
+ class TableTypeCheckConstraintHandler
191
+ def initialize(constraints, node)
192
+ TableType::CheckConstraint.new(node.attributes).tap do |c|
193
+ constraints << (@constraint = c)
194
+ end
195
+ end
196
+
197
+ def handle_expression_element(parse)
198
+ # do nothing
199
+ end
200
+
201
+ def handle_text(text, parent_element)
202
+ case [parent_element.namespace, parent_element.name]
203
+ when ['', 'expression']
204
+ @constraint.expression << text
205
+ end
206
+ end
207
+
208
+ def handle_property_element(parse)
209
+ raise "Properties on table type constraints are unsupported"
210
+ end
211
+ end
212
+
213
+ class TableTypeKeyConstraintHandler
214
+ def initialize(constraints, node)
215
+ TableType::KeyConstraint.new(node.attributes).tap do |c|
216
+ constraints << (@constraint = c)
217
+ end
218
+ end
219
+
220
+ def handle_column_element(parse)
221
+ @constraint.columns << TableType::ConstraintColumn.new(parse.node.attributes)
222
+ end
223
+
224
+ def handle_property_element(parse)
225
+ raise "Properties on table type constraints are unsupported"
226
+ end
227
+ end
228
+
229
+ class TableTypeHandler
230
+ include PropertyHandler::ElementHandler
231
+
232
+ def initialize(user_types, node)
233
+ TableType.new(node.attributes).tap do |tt|
234
+ user_types << store_properties_on(@type = tt)
235
+ end
236
+ end
237
+
238
+ def handle_column_element(parse)
239
+ a = parse.node.attributes
240
+ TableType::Column.new(parse.node.attributes).tap do |c|
241
+ @type.columns << c
242
+ parse.context = TableTypeColumnHandler.new(c)
243
+ end
244
+ end
245
+
246
+ def handle_key_constraint_element(parse)
247
+ parse.delegate_to TableTypeKeyConstraintHandler, @type.constraints
248
+ end
249
+
250
+ def handle_check_constraint_element(parse)
251
+ parse.delegate_to TableTypeCheckConstraintHandler, @type.constraints
252
+ end
253
+ end
254
+ end