mkxms-mssql 1.0.0 → 1.1.0

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