Class: Mkxms::Mssql::DeclarativesCreator

Inherits:
Object
  • Object
show all
Defined in:
lib/mkxms/mssql/declaratives_creator.rb

Instance Method Summary collapse

Constructor Details

#initialize(document, schema_dir) ⇒ DeclarativesCreator

Returns a new instance of DeclarativesCreator.



10
11
12
13
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 10

def initialize(document, schema_dir)
  @document = document
  @schema_dir = schema_dir || Pathname.pwd
end

Instance Method Details

#attr_eq?(a, o1 = nil, *objs) ⇒ Boolean

Returns:

  • (Boolean)


187
188
189
190
191
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 187

def attr_eq?(a, o1=nil, *objs)
  return true if o1.nil? || objs.length == 0
  val = o1.attributes[a]
  return objs.all? {|o| o.attributes[a] == val}
end

#build_declarative(table) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 43

def build_declarative(table)
  doc, tdecl = create_blank_table_decl
  
  columns_decl = Psych::Nodes::Sequence.new.tap do |s|
    tdecl.children << node_from('columns') << s
  end
  
  table_key = %w[schema name].map {|a| table.attributes[a]}
  
  # Columns (including single-column default constraints)
  table.elements.each('column') do |column|
    entry = Psych::Nodes::Mapping.new.tap {|e| columns_decl.children << e}
    col_name = column.attributes['name']
    if col_name =~ /^\[[a-zA-Z_][a-zA-Z0-9_]*\]$/ && !KEYWORDS_SET.include?(col_name[1..-2].upcase)
      col_name = col_name[1..-2]
    end
    entry.children.concat(
      ['name', col_name].map {|v| node_from(v)}
    )
    col_type = column.attributes['type']
    if KEYWORDS_SET.include?(basic_type = col_type[1..-2].upcase)
      col_type = basic_type
    end
    if capacity = column.attributes['capacity']
      col_type = "#{col_type}(#{capacity})"
    end
    entry.children.concat(
      ['type', col_type].map {|v| node_from(v)}
    )
    unless column.attributes['nullable']
      entry.children.concat(
        ['nullable', false].map {|v| node_from(v)}
      )
    end
    
    if cstr = cstr_on_column(@default_constraints, table_key, column)
      entry.children.concat(['default', cstr.text].map {|v| node_from(v)})
    end
    if cexpr = column.elements['computed-expression']
      entry.children.concat(['X-computed-as', cexpr.text].map {|v| node_from(v)})
    end
  end
  
  # Everything but default constraints
  cstrs_decl = Psych::Nodes::Mapping.new
  constraint_default_name_part = mashable_name(table.attributes['name'])
  @primary_key_constraints.fetch(table_key, []).each do |cstr|
    cstr_name = cstr.attributes['name'] || "PK_#{constraint_default_name_part}"
    cstrs_decl.children << node_from(cstr_name) << node_from({
      'type' => 'primary key',
      'columns' => cstr.elements.enum_for(:each, 'column').map {|c| c.attributes['name']},
    })
  end
  @uniqueness_constraints.fetch(table_key, []).each do |cstr|
    cstr_name = cstr.attributes['name'] || (
      "UQ_#{constraint_default_name_part}_" +
      mashable_name(
        cstr.elements.enum_for(:each, 'column').map {|c| c.attributes['name']}.join('_')
      )
    )
    cstrs_decl.children << node_from(cstr_name) << node_from({
      'type' => 'unique',
      'columns' => cstr.elements.enum_for(:each, 'column').map {|c| c.attributes['name']},
    })
  end
  @foreign_key_constraints.fetch(table_key, []).each do |cstr|
    cstr_name = cstr.attributes['name'] || :generated
    if cstr_name == :generated
      from_cols, to_cols = [], []
      cstr.elements.each('link') do |link|
        from_cols << link.attributes['from']
        to_cols << link.attributes['to']
      end
      cstr_name = (
        "FK_#{constraint_default_name_part}_" + 
        mashable_name(from_cols.join('_')) + '_' +
        mashable_name(%w[schema name].map {|a| cstr.elements['referent'].attributes[a]}.join('_')) + '_' +
        mashable_name(to_cols.join('_'))
      )
    end
    cstrs_decl.children << node_from(cstr_name) << node_from({
      'link to' => cstr.elements['referent'].tap do |r|
        break [r.attributes['schema'], r.attributes['name']].join('.')
      end,
      'columns' => Hash[
        cstr.elements.enum_for(:each, 'link').map do |link|
          %w[from to].map {|a| link.attributes[a]}
        end
      ],
    })
  end
  existing_check_names = nil
  @check_constraints.fetch(table_key, []).each_with_index do |cstr, i|
    cstr_name = cstr.attributes['name'] || :generated
    if cstr_name == :generated
      existing_check_names ||= @check_constraints[table_key].map {|c| c.attributes['name']}.compact
      cstr_name = "CK_#{constraint_default_name_part}_#{i+1}"
      while existing_check_names.include?(cstr_name)
        cstr_name << '_' unless cstr_name.end_with?('_')
        cstr_name << 'X'
      end
      existing_check_names << cstr_name
    end
    cstrs_decl.children << node_from(cstr_name) << node_from({
      'verify' => cstr.text,
    })
  end
  
  unless cstrs_decl.children.empty?
    tdecl.children << node_from("constraints") << cstrs_decl
  end
  
  return doc
end

#create_artifactsObject



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 22

def create_artifacts
  index_constraints
  
  # Loop through all tables
  decl_paths = []
  @document.elements.each('/database/table') do |table|
    schema, name = %w[schema name].map {|a| table.attributes[a]}
    tdecl_path = decls_dir.join([schema, name, 'yaml'].join('.'))
    doc = build_declarative(table)
    decls_dir.mkpath
    tdecl_path.open('w') {|f| f.write(doc.to_yaml)}
    decl_paths << tdecl_path
  end
  
  # Loop through the created paths creating an adoption migration for each
  decl_paths.each do |fpath|
    tool = XMigra::ImpdeclMigrationAdder.new(@schema_dir)
    tool.add_migration_implementing_changes(fpath, {adopt: true})
  end
end

#create_blank_table_declObject



173
174
175
176
177
178
179
180
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 173

def create_blank_table_decl
  stream = Psych::Nodes::Stream.new
  doc = Psych::Nodes::Document.new.tap {|d| stream.children << d}
  decl = Psych::Nodes::Mapping.new.tap {|m| doc.children << m}
  decl.implicit = false
  decl.tag = '!table'
  return [stream, decl]
end

#cstr_on_column(group, key, column) ⇒ Object



193
194
195
196
197
198
199
200
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 193

def cstr_on_column(group, key, column)
  cstrs = group[key]
  return nil unless cstrs
  cstrs.find do |cstr|
    cstr.attributes['column'] == column.attributes['name'] || \
      cstr.elements.enum_for(:each, 'column').select {|c| attr_eq?('name', c, column)}.count > 0
  end
end

#decls_dirObject



15
16
17
18
19
20
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 15

def decls_dir
  @schema_dir.join(
    XMigra::SchemaManipulator::STRUCTURE_SUBDIR,
    XMigra::DeclarativeMigration::SUBDIR,
  )
end

#index_constraintsObject



158
159
160
161
162
163
164
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 158

def index_constraints
  @primary_key_constraints = read_constraints('primary-key')
  @uniqueness_constraints = read_constraints('unique-constraint')
  @foreign_key_constraints = read_constraints('foreign-key')
  @check_constraints = read_constraints('check-constraint')
  @default_constraints = read_constraints('default-constraint')
end

#mashable_name(s) ⇒ Object



202
203
204
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 202

def mashable_name(s)
  s.gsub(/[\]\[]/, '').gsub(/[^a-zA-Z_]/, '_')
end

#node_from(val) ⇒ Object



182
183
184
185
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 182

def node_from(val)
  ast_stream = Psych.parse_stream(Psych.dump(val))
  return ast_stream.children[0].children[0]
end

#read_constraints(ctype, inline: false) ⇒ Object



166
167
168
169
170
171
# File 'lib/mkxms/mssql/declaratives_creator.rb', line 166

def read_constraints(ctype, inline: false)
  @document.elements.enum_for(:each, "/database/#{ctype}").each_with_object({}) do |cstr, result|
    key = [cstr.attributes['schema'], cstr.attributes['table']]
    (result[key] ||= []) << cstr
  end
end