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.
- checksums.yaml +4 -4
- data/README.md +4 -10
- data/lib/mkxms/mssql.rb +18 -0
- data/lib/mkxms/mssql/adoption_script_writer.rb +759 -91
- data/lib/mkxms/mssql/clr_aggregate_handler.rb +98 -0
- data/lib/mkxms/mssql/clr_assembly_handler.rb +92 -0
- data/lib/mkxms/mssql/clr_function_handler.rb +172 -0
- data/lib/mkxms/mssql/clr_impl.rb +58 -0
- data/lib/mkxms/mssql/clr_stored_procedure_handler.rb +88 -0
- data/lib/mkxms/mssql/clr_type_handler.rb +92 -0
- data/lib/mkxms/mssql/database_handler.rb +124 -3
- data/lib/mkxms/mssql/declaratives_creator.rb +206 -0
- data/lib/mkxms/mssql/dml_trigger_handler.rb +107 -0
- data/lib/mkxms/mssql/filegroup_handler.rb +1 -4
- data/lib/mkxms/mssql/function_handler.rb +1 -4
- data/lib/mkxms/mssql/indented_string_builder.rb +8 -2
- data/lib/mkxms/mssql/index_handler.rb +1 -4
- data/lib/mkxms/mssql/keywords.rb +492 -0
- data/lib/mkxms/mssql/primary_key_handler.rb +1 -4
- data/lib/mkxms/mssql/property_handler.rb +8 -0
- data/lib/mkxms/mssql/query_cursor.rb +12 -4
- data/lib/mkxms/mssql/references_handler.rb +24 -0
- data/lib/mkxms/mssql/role_handler.rb +1 -4
- data/lib/mkxms/mssql/scalar_type_handler.rb +108 -0
- data/lib/mkxms/mssql/schema_handler.rb +1 -4
- data/lib/mkxms/mssql/sql_string_manipulators.rb +4 -4
- data/lib/mkxms/mssql/statistics_handler.rb +1 -4
- data/lib/mkxms/mssql/stored_procedure_handler.rb +1 -4
- data/lib/mkxms/mssql/synonym_handler.rb +40 -0
- data/lib/mkxms/mssql/table_handler.rb +2 -8
- data/lib/mkxms/mssql/table_type_handler.rb +254 -0
- data/lib/mkxms/mssql/utils.rb +96 -0
- data/lib/mkxms/mssql/version.rb +1 -1
- data/lib/mkxms/mssql/view_handler.rb +1 -4
- data/spec/utils/indented_string_builder_spec.rb +21 -0
- data/spec/utils/query_cursor_spec.rb +2 -2
- data/spec/utils/sql_string_manipulators_spec.rb +59 -0
- metadata +18 -3
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'mkxms/mssql/utils'
|
2
|
+
|
3
|
+
module Mkxms; end
|
4
|
+
|
5
|
+
module Mkxms::Mssql
|
6
|
+
class ClrType
|
7
|
+
include Utils::SchemaQualifiedName
|
8
|
+
include ExtendedProperties, Property::Hosting, Property::SchemaScoped
|
9
|
+
|
10
|
+
RaiserrorSource = Utils::RaiserrorWriter.new("%s: Missing or misconfigured CLR type %s")
|
11
|
+
|
12
|
+
def initialize(schema, name, assembly, clr_class)
|
13
|
+
@schema = schema
|
14
|
+
@name = name
|
15
|
+
@assembly = assembly
|
16
|
+
@clr_class = clr_class
|
17
|
+
@warning_stmt = RaiserrorSource.next_statement("WARNING".sql_quoted, qualified_name.sql_quoted, severity: :warning)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :schema, :name, :warning_stmt
|
21
|
+
attr_accessor :assembly, :clr_class
|
22
|
+
|
23
|
+
def self.setup_sql
|
24
|
+
[].tap do |s|
|
25
|
+
s << "IF NOT EXISTS (SELECT * FROM sys.tables t WHERE t.object_id = OBJECT_ID(N'xmigra.ignored_clr_types'))"
|
26
|
+
s << " CREATE TABLE xmigra.ignored_clr_types ([schema] SYSNAME, name SYSNAME, CONSTRAINT PK_ignored_clr_types PRIMARY KEY ([schema], name));"
|
27
|
+
|
28
|
+
s << "" # Give a newline at the end
|
29
|
+
end.join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sql
|
33
|
+
[].tap do |s|
|
34
|
+
s << "IF NOT EXISTS ("
|
35
|
+
s << " SELECT t.assembly_qualified_name"
|
36
|
+
s << " FROM sys.assembly_types t"
|
37
|
+
s << " JOIN sys.schemas s ON t.schema_id = s.schema_id"
|
38
|
+
s << " WHERE QUOTENAME(s.name) = #{schema.sql_quoted}"
|
39
|
+
s << " AND QUOTENAME(t.name) = #{name.sql_quoted}"
|
40
|
+
s << " UNION ALL"
|
41
|
+
s << " SELECT N''"
|
42
|
+
s << " FROM xmigra.ignored_clr_types t"
|
43
|
+
s << " WHERE t.[schema] = #{schema.sql_quoted}"
|
44
|
+
s << " AND t.name = #{name.sql_quoted}"
|
45
|
+
s << ") BEGIN"
|
46
|
+
s << " CREATE TYPE #{schema}.#{name} EXTERNAL NAME #{assembly}.#{clr_class};"
|
47
|
+
s.concat(extended_properties_sql.map {|s| " " + s})
|
48
|
+
s << "END"
|
49
|
+
|
50
|
+
s << "IF NOT EXISTS ("
|
51
|
+
s << " SELECT CONCAT(s.name, N'.', t.name) as clr_type, QUOTENAME(asm.name) as assembly, QUOTENAME(t.assembly_class) as clr_class"
|
52
|
+
s << " FROM sys.assembly_types t"
|
53
|
+
s << " JOIN sys.schemas s ON t.schema_id = s.schema_id"
|
54
|
+
s << " JOIN sys.assemblies asm ON t.assembly_id = asm.assembly_id"
|
55
|
+
s << " WHERE QUOTENAME(s.name) = #{schema.sql_quoted}"
|
56
|
+
s << " AND QUOTENAME(t.name) = #{name.sql_quoted}"
|
57
|
+
s << " -- #{warning_stmt.error_marker} Run the query up to this point for CLR type configuration --"
|
58
|
+
cols = [
|
59
|
+
["assembly", assembly],
|
60
|
+
["clr_class", clr_class],
|
61
|
+
].map {|t, v| [t.ljust(v.length), v.ljust(t.length)]}
|
62
|
+
s << (" -- " + cols.map {|e| e[0]}.join(' ') + ' --')
|
63
|
+
s << (" -- Expected values: " + cols.map {|e| e[1]}.join(' ') + ' --')
|
64
|
+
s << " AND QUOTENAME(asm.name) = #{assembly.sql_quoted}"
|
65
|
+
s << " AND QUOTENAME(t.assembly_class) = #{clr_class.sql_quoted}"
|
66
|
+
s << " UNION ALL"
|
67
|
+
s << " SELECT CONCAT(t.[schema], N'.', t.name), NULL, NULL"
|
68
|
+
s << " FROM xmigra.ignored_clr_types t"
|
69
|
+
s << " WHERE t.[schema] = #{schema.sql_quoted}"
|
70
|
+
s << " AND t.name = #{name.sql_quoted}"
|
71
|
+
s << ") #{warning_stmt};"
|
72
|
+
|
73
|
+
s << "" # Give a newline at the end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class ClrTypeHandler
|
79
|
+
include PropertyHandler::ElementHandler
|
80
|
+
|
81
|
+
def initialize(types, node)
|
82
|
+
a = node.attributes
|
83
|
+
|
84
|
+
@type_info = ClrType.new(
|
85
|
+
a['schema'],
|
86
|
+
a['name'],
|
87
|
+
a['assembly'],
|
88
|
+
a['class']
|
89
|
+
).tap {|t| types << store_properties_on(t)}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -7,7 +7,13 @@ require 'yaml'
|
|
7
7
|
|
8
8
|
adoption_script_writer
|
9
9
|
check_constraint_handler
|
10
|
+
clr_aggregate_handler
|
11
|
+
clr_assembly_handler
|
12
|
+
clr_function_handler
|
13
|
+
clr_stored_procedure_handler
|
14
|
+
clr_type_handler
|
10
15
|
default_constraint_handler
|
16
|
+
dml_trigger_handler
|
11
17
|
filegroup_handler
|
12
18
|
foreign_key_handler
|
13
19
|
function_handler
|
@@ -16,10 +22,13 @@ require 'yaml'
|
|
16
22
|
primary_key_handler
|
17
23
|
property_handler
|
18
24
|
role_handler
|
25
|
+
scalar_type_handler
|
19
26
|
schema_handler
|
20
27
|
statistics_handler
|
21
28
|
stored_procedure_handler
|
29
|
+
synonym_handler
|
22
30
|
table_handler
|
31
|
+
table_type_handler
|
23
32
|
unique_constraint_handler
|
24
33
|
utils
|
25
34
|
view_handler
|
@@ -34,15 +43,32 @@ module Mkxms::Mssql
|
|
34
43
|
include ExtendedProperties, PropertyHandler::ElementHandler
|
35
44
|
|
36
45
|
ADOPTION_SQL_FILE = "adopt.sql"
|
46
|
+
DRY_RUN_MARKER = "for dry run"
|
47
|
+
|
48
|
+
class IgnoreText
|
49
|
+
def initialize(node)
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_text(t, node)
|
53
|
+
end
|
54
|
+
end
|
37
55
|
|
38
56
|
def initialize(**kwargs)
|
39
57
|
@schema_dir = kwargs[:schema_dir] || Pathname.pwd
|
40
58
|
end
|
41
59
|
|
42
60
|
attr_reader :schema_dir
|
43
|
-
attr_init(
|
61
|
+
attr_init(
|
62
|
+
:filegroups, :schemas, :roles,
|
63
|
+
:types,
|
64
|
+
:clr_assemblies, :clr_types,
|
65
|
+
:tables,
|
66
|
+
:column_defaults, :pku_constraints, :foreign_keys,
|
67
|
+
:check_constraints, :dml_triggers,
|
68
|
+
:synonyms,
|
69
|
+
){[]}
|
44
70
|
attr_init(:indexes, :statistics){[]}
|
45
|
-
attr_init(:views, :udfs, :procedures){[]}
|
71
|
+
attr_init(:views, :udfs, :procedures, :aggregates){[]}
|
46
72
|
attr_init(:permissions){[]}
|
47
73
|
|
48
74
|
def handle_database_element(parse)
|
@@ -60,6 +86,14 @@ module Mkxms::Mssql
|
|
60
86
|
parse.delegate_to SchemaHandler, schemas
|
61
87
|
end
|
62
88
|
|
89
|
+
def handle_type_element(parse)
|
90
|
+
parse.delegate_to ScalarTypeHandler, types
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_table_type_element(parse)
|
94
|
+
parse.delegate_to TableTypeHandler, types
|
95
|
+
end
|
96
|
+
|
63
97
|
def handle_role_element(parse)
|
64
98
|
parse.delegate_to RoleHandler, roles
|
65
99
|
end
|
@@ -104,10 +138,22 @@ module Mkxms::Mssql
|
|
104
138
|
parse.delegate_to StoredProcedureHandler, procedures
|
105
139
|
end
|
106
140
|
|
141
|
+
def handle_clr_stored_procedure_element(parse)
|
142
|
+
parse.delegate_to ClrStoredProcedureHandler, procedures
|
143
|
+
end
|
144
|
+
|
107
145
|
def handle_user_defined_function_element(parse)
|
108
146
|
parse.delegate_to FunctionHandler, udfs
|
109
147
|
end
|
110
148
|
|
149
|
+
def handle_clr_function_element(parse)
|
150
|
+
parse.delegate_to ClrFunctionHandler, udfs
|
151
|
+
end
|
152
|
+
|
153
|
+
def handle_clr_aggregate_element(parse)
|
154
|
+
parse.delegate_to ClrArggregateHandler, aggregates
|
155
|
+
end
|
156
|
+
|
111
157
|
def handle_granted_element(parse)
|
112
158
|
parse.delegate_to PermissionHandler, permissions
|
113
159
|
end
|
@@ -116,11 +162,32 @@ module Mkxms::Mssql
|
|
116
162
|
parse.delegate_to PermissionHandler, permissions
|
117
163
|
end
|
118
164
|
|
165
|
+
def handle_clr_assembly_element(parse)
|
166
|
+
parse.delegate_to ClrAssemblyHandler, clr_assemblies
|
167
|
+
end
|
168
|
+
|
169
|
+
def handle_clr_type_element(parse)
|
170
|
+
parse.delegate_to ClrTypeHandler, clr_types
|
171
|
+
end
|
172
|
+
|
173
|
+
def handle_dml_trigger_element(parse)
|
174
|
+
parse.delegate_to DmlTriggerHandler, dml_triggers
|
175
|
+
end
|
176
|
+
|
177
|
+
def handle_synonym_element(parse)
|
178
|
+
parse.delegate_to SynonymHandler, synonyms
|
179
|
+
end
|
180
|
+
|
119
181
|
def create_source_files
|
120
182
|
dbinfo_path = @schema_dir.join(XMigra::SchemaManipulator::DBINFO_FILE)
|
121
183
|
|
122
184
|
if dbinfo_path.exist?
|
123
|
-
|
185
|
+
if dbinfo_path.open {|f| YAML.load(f)[DRY_RUN_MARKER]}
|
186
|
+
# Delete everything in the source files, so we can do a dry run over
|
187
|
+
@schema_dir.each_child {|e| e.rmtree}
|
188
|
+
else
|
189
|
+
raise ProgramArgumentError.new("#{@schema_dir} already contains an XMigra schema")
|
190
|
+
end
|
124
191
|
end
|
125
192
|
|
126
193
|
# TODO: Sort dependencies of triggers, views, user defined functions, and
|
@@ -133,10 +200,21 @@ module Mkxms::Mssql
|
|
133
200
|
# Create and populate @schema_dir + XMigra::SchemaManipulator::DBINFO_FILE
|
134
201
|
dbinfo_path.open('w') do |dbi|
|
135
202
|
dbi.puts "system: #{XMigra::MSSQLSpecifics::SYSTEM_NAME}"
|
203
|
+
if Utils.dry_run?
|
204
|
+
dbi.puts "#{DRY_RUN_MARKER}: true"
|
205
|
+
end
|
136
206
|
end
|
137
207
|
|
138
208
|
# TODO: Create migration to check required filegroups and files
|
139
209
|
|
210
|
+
# Migration: Check CLR assemblies
|
211
|
+
create_migration(
|
212
|
+
"check-clr-assemblies",
|
213
|
+
"Check expected CLR assemblies have been created.",
|
214
|
+
ClrAssembly.setup_sql + "\n" + joined_modobj_sql(clr_assemblies),
|
215
|
+
clr_assemblies.map(&:name).sort
|
216
|
+
)
|
217
|
+
|
140
218
|
# Migration: Create roles
|
141
219
|
create_migration(
|
142
220
|
"create-roles",
|
@@ -153,6 +231,30 @@ module Mkxms::Mssql
|
|
153
231
|
schemas.map(&:name).sort
|
154
232
|
)
|
155
233
|
|
234
|
+
# Migration: Create scalar types
|
235
|
+
create_migration(
|
236
|
+
"create-scalar-types",
|
237
|
+
"Create user-defined scalar types.",
|
238
|
+
joined_modobj_sql(types),
|
239
|
+
types.map {|t| [t.schema, t.qualified_name]}.flatten.uniq.sort
|
240
|
+
)
|
241
|
+
|
242
|
+
# Migration: Create synonyms
|
243
|
+
create_migration(
|
244
|
+
"create-synonyms",
|
245
|
+
"Create synonyms for other objects in the database.",
|
246
|
+
joined_modobj_sql(synonyms),
|
247
|
+
synonyms.map {|s| [s.schema, s.qualified_name]}.flatten
|
248
|
+
)
|
249
|
+
|
250
|
+
# Migration: Create CLR types that don't exist
|
251
|
+
create_migration(
|
252
|
+
"create-clr-types",
|
253
|
+
"Create CLR types (unless already existing).",
|
254
|
+
ClrType.setup_sql + "\n" + joined_modobj_sql(clr_types),
|
255
|
+
clr_types.map(&:qualified_name).sort
|
256
|
+
)
|
257
|
+
|
156
258
|
tables.each do |table|
|
157
259
|
# Migration: Create table
|
158
260
|
qual_name = [table.schema, table.name].join('.')
|
@@ -196,6 +298,16 @@ module Mkxms::Mssql
|
|
196
298
|
check_constraints.map {|c| [c.schema, c.qualified_table, c.qualified_name].compact}.flatten.uniq.sort
|
197
299
|
)
|
198
300
|
|
301
|
+
# Migration: Add DML triggers
|
302
|
+
create_migration(
|
303
|
+
"add-triggers",
|
304
|
+
"Add triggers.",
|
305
|
+
joined_modobj_sql(dml_triggers, sep: DmlTriggerHandler.ddl_block_separator) + "\n",
|
306
|
+
dml_triggers.map do |t|
|
307
|
+
[t.schema, t.table.qualified_name, t.qualified_name].compact
|
308
|
+
end.flatten.uniq.sort
|
309
|
+
) unless dml_triggers.empty?
|
310
|
+
|
199
311
|
# Check that no super-permissions reference a view, user-defined function, or stored procedure
|
200
312
|
access_object_names = (views + udfs + procedures).map {|ao| ao.qualified_name}
|
201
313
|
permissions.map {|p| p.super_permissions}.flatten.select do |p|
|
@@ -228,6 +340,15 @@ module Mkxms::Mssql
|
|
228
340
|
|
229
341
|
write_statistics
|
230
342
|
|
343
|
+
aggregates.each do |agg|
|
344
|
+
create_migration(
|
345
|
+
"register-#{agg.qualified_name}-aggregate",
|
346
|
+
"Register the CLR aggregate function #{agg.qualified_name}",
|
347
|
+
agg.to_sql.join("\nGO\n"),
|
348
|
+
[agg.schema, agg.qualified_name]
|
349
|
+
)
|
350
|
+
end
|
351
|
+
|
231
352
|
views.each do |view|
|
232
353
|
write_access_def(view, 'view')
|
233
354
|
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'psych' # YAML support
|
3
|
+
require 'xmigra'
|
4
|
+
require 'mkxms/mssql/keywords'
|
5
|
+
|
6
|
+
module Mkxms; end
|
7
|
+
|
8
|
+
module Mkxms::Mssql
|
9
|
+
class DeclarativesCreator
|
10
|
+
def initialize(document, schema_dir)
|
11
|
+
@document = document
|
12
|
+
@schema_dir = schema_dir || Pathname.pwd
|
13
|
+
end
|
14
|
+
|
15
|
+
def decls_dir
|
16
|
+
@schema_dir.join(
|
17
|
+
XMigra::SchemaManipulator::STRUCTURE_SUBDIR,
|
18
|
+
XMigra::DeclarativeMigration::SUBDIR,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_artifacts
|
23
|
+
index_constraints
|
24
|
+
|
25
|
+
# Loop through all tables
|
26
|
+
decl_paths = []
|
27
|
+
@document.elements.each('/database/table') do |table|
|
28
|
+
schema, name = %w[schema name].map {|a| table.attributes[a]}
|
29
|
+
tdecl_path = decls_dir.join([schema, name, 'yaml'].join('.'))
|
30
|
+
doc = build_declarative(table)
|
31
|
+
decls_dir.mkpath
|
32
|
+
tdecl_path.open('w') {|f| f.write(doc.to_yaml)}
|
33
|
+
decl_paths << tdecl_path
|
34
|
+
end
|
35
|
+
|
36
|
+
# Loop through the created paths creating an adoption migration for each
|
37
|
+
decl_paths.each do |fpath|
|
38
|
+
tool = XMigra::ImpdeclMigrationAdder.new(@schema_dir)
|
39
|
+
tool.add_migration_implementing_changes(fpath, {adopt: true})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_declarative(table)
|
44
|
+
doc, tdecl = create_blank_table_decl
|
45
|
+
|
46
|
+
columns_decl = Psych::Nodes::Sequence.new.tap do |s|
|
47
|
+
tdecl.children << node_from('columns') << s
|
48
|
+
end
|
49
|
+
|
50
|
+
table_key = %w[schema name].map {|a| table.attributes[a]}
|
51
|
+
|
52
|
+
# Columns (including single-column default constraints)
|
53
|
+
table.elements.each('column') do |column|
|
54
|
+
entry = Psych::Nodes::Mapping.new.tap {|e| columns_decl.children << e}
|
55
|
+
col_name = column.attributes['name']
|
56
|
+
if col_name =~ /^\[[a-zA-Z_][a-zA-Z0-9_]*\]$/ && !KEYWORDS_SET.include?(col_name[1..-2].upcase)
|
57
|
+
col_name = col_name[1..-2]
|
58
|
+
end
|
59
|
+
entry.children.concat(
|
60
|
+
['name', col_name].map {|v| node_from(v)}
|
61
|
+
)
|
62
|
+
col_type = column.attributes['type']
|
63
|
+
if KEYWORDS_SET.include?(basic_type = col_type[1..-2].upcase)
|
64
|
+
col_type = basic_type
|
65
|
+
end
|
66
|
+
if capacity = column.attributes['capacity']
|
67
|
+
col_type = "#{col_type}(#{capacity})"
|
68
|
+
end
|
69
|
+
entry.children.concat(
|
70
|
+
['type', col_type].map {|v| node_from(v)}
|
71
|
+
)
|
72
|
+
unless column.attributes['nullable']
|
73
|
+
entry.children.concat(
|
74
|
+
['nullable', false].map {|v| node_from(v)}
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
if cstr = cstr_on_column(@default_constraints, table_key, column)
|
79
|
+
entry.children.concat(['default', cstr.text].map {|v| node_from(v)})
|
80
|
+
end
|
81
|
+
if cexpr = column.elements['computed-expression']
|
82
|
+
entry.children.concat(['X-computed-as', cexpr.text].map {|v| node_from(v)})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Everything but default constraints
|
87
|
+
cstrs_decl = Psych::Nodes::Mapping.new
|
88
|
+
constraint_default_name_part = mashable_name(table.attributes['name'])
|
89
|
+
@primary_key_constraints.fetch(table_key, []).each do |cstr|
|
90
|
+
cstr_name = cstr.attributes['name'] || "PK_#{constraint_default_name_part}"
|
91
|
+
cstrs_decl.children << node_from(cstr_name) << node_from({
|
92
|
+
'type' => 'primary key',
|
93
|
+
'columns' => cstr.elements.enum_for(:each, 'column').map {|c| c.attributes['name']},
|
94
|
+
})
|
95
|
+
end
|
96
|
+
@uniqueness_constraints.fetch(table_key, []).each do |cstr|
|
97
|
+
cstr_name = cstr.attributes['name'] || (
|
98
|
+
"UQ_#{constraint_default_name_part}_" +
|
99
|
+
mashable_name(
|
100
|
+
cstr.elements.enum_for(:each, 'column').map {|c| c.attributes['name']}.join('_')
|
101
|
+
)
|
102
|
+
)
|
103
|
+
cstrs_decl.children << node_from(cstr_name) << node_from({
|
104
|
+
'type' => 'unique',
|
105
|
+
'columns' => cstr.elements.enum_for(:each, 'column').map {|c| c.attributes['name']},
|
106
|
+
})
|
107
|
+
end
|
108
|
+
@foreign_key_constraints.fetch(table_key, []).each do |cstr|
|
109
|
+
cstr_name = cstr.attributes['name'] || :generated
|
110
|
+
if cstr_name == :generated
|
111
|
+
from_cols, to_cols = [], []
|
112
|
+
cstr.elements.each('link') do |link|
|
113
|
+
from_cols << link.attributes['from']
|
114
|
+
to_cols << link.attributes['to']
|
115
|
+
end
|
116
|
+
cstr_name = (
|
117
|
+
"FK_#{constraint_default_name_part}_" +
|
118
|
+
mashable_name(from_cols.join('_')) + '_' +
|
119
|
+
mashable_name(%w[schema name].map {|a| cstr.elements['referent'].attributes[a]}.join('_')) + '_' +
|
120
|
+
mashable_name(to_cols.join('_'))
|
121
|
+
)
|
122
|
+
end
|
123
|
+
cstrs_decl.children << node_from(cstr_name) << node_from({
|
124
|
+
'link to' => cstr.elements['referent'].tap do |r|
|
125
|
+
break [r.attributes['schema'], r.attributes['name']].join('.')
|
126
|
+
end,
|
127
|
+
'columns' => Hash[
|
128
|
+
cstr.elements.enum_for(:each, 'link').map do |link|
|
129
|
+
%w[from to].map {|a| link.attributes[a]}
|
130
|
+
end
|
131
|
+
],
|
132
|
+
})
|
133
|
+
end
|
134
|
+
existing_check_names = nil
|
135
|
+
@check_constraints.fetch(table_key, []).each_with_index do |cstr, i|
|
136
|
+
cstr_name = cstr.attributes['name'] || :generated
|
137
|
+
if cstr_name == :generated
|
138
|
+
existing_check_names ||= @check_constraints[table_key].map {|c| c.attributes['name']}.compact
|
139
|
+
cstr_name = "CK_#{constraint_default_name_part}_#{i+1}"
|
140
|
+
while existing_check_names.include?(cstr_name)
|
141
|
+
cstr_name << '_' unless cstr_name.end_with?('_')
|
142
|
+
cstr_name << 'X'
|
143
|
+
end
|
144
|
+
existing_check_names << cstr_name
|
145
|
+
end
|
146
|
+
cstrs_decl.children << node_from(cstr_name) << node_from({
|
147
|
+
'verify' => cstr.text,
|
148
|
+
})
|
149
|
+
end
|
150
|
+
|
151
|
+
unless cstrs_decl.children.empty?
|
152
|
+
tdecl.children << node_from("constraints") << cstrs_decl
|
153
|
+
end
|
154
|
+
|
155
|
+
return doc
|
156
|
+
end
|
157
|
+
|
158
|
+
def index_constraints
|
159
|
+
@primary_key_constraints = read_constraints('primary-key')
|
160
|
+
@uniqueness_constraints = read_constraints('unique-constraint')
|
161
|
+
@foreign_key_constraints = read_constraints('foreign-key')
|
162
|
+
@check_constraints = read_constraints('check-constraint')
|
163
|
+
@default_constraints = read_constraints('default-constraint')
|
164
|
+
end
|
165
|
+
|
166
|
+
def read_constraints(ctype, inline: false)
|
167
|
+
@document.elements.enum_for(:each, "/database/#{ctype}").each_with_object({}) do |cstr, result|
|
168
|
+
key = [cstr.attributes['schema'], cstr.attributes['table']]
|
169
|
+
(result[key] ||= []) << cstr
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_blank_table_decl
|
174
|
+
stream = Psych::Nodes::Stream.new
|
175
|
+
doc = Psych::Nodes::Document.new.tap {|d| stream.children << d}
|
176
|
+
decl = Psych::Nodes::Mapping.new.tap {|m| doc.children << m}
|
177
|
+
decl.implicit = false
|
178
|
+
decl.tag = '!table'
|
179
|
+
return [stream, decl]
|
180
|
+
end
|
181
|
+
|
182
|
+
def node_from(val)
|
183
|
+
ast_stream = Psych.parse_stream(Psych.dump(val))
|
184
|
+
return ast_stream.children[0].children[0]
|
185
|
+
end
|
186
|
+
|
187
|
+
def attr_eq?(a, o1=nil, *objs)
|
188
|
+
return true if o1.nil? || objs.length == 0
|
189
|
+
val = o1.attributes[a]
|
190
|
+
return objs.all? {|o| o.attributes[a] == val}
|
191
|
+
end
|
192
|
+
|
193
|
+
def cstr_on_column(group, key, column)
|
194
|
+
cstrs = group[key]
|
195
|
+
return nil unless cstrs
|
196
|
+
cstrs.find do |cstr|
|
197
|
+
cstr.attributes['column'] == column.attributes['name'] || \
|
198
|
+
cstr.elements.enum_for(:each, 'column').select {|c| attr_eq?('name', c, column)}.count > 0
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def mashable_name(s)
|
203
|
+
s.gsub(/[\]\[]/, '').gsub(/[^a-zA-Z_]/, '_')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|