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