class Mkxms::Mssql::DeclarativesCreator

Public Class Methods

new(document, schema_dir) click to toggle source
# File lib/mkxms/mssql/declaratives_creator.rb, line 10
def initialize(document, schema_dir)
  @document = document
  @schema_dir = schema_dir || Pathname.pwd
end

Public Instance Methods

attr_eq?(a, o1=nil, *objs) click to toggle source
# 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) click to toggle source
# 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_artifacts() click to toggle source
# 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_decl() click to toggle source
# 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) click to toggle source
# 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_dir() click to toggle source
# File lib/mkxms/mssql/declaratives_creator.rb, line 15
def decls_dir
  @schema_dir.join(
    XMigra::SchemaManipulator::STRUCTURE_SUBDIR,
    XMigra::DeclarativeMigration::SUBDIR,
  )
end
index_constraints() click to toggle source
# 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) click to toggle source
# File lib/mkxms/mssql/declaratives_creator.rb, line 202
def mashable_name(s)
  s.gsub(/[\]\[]/, '').gsub(/[^a-zA-Z_]/, '_')
end
node_from(val) click to toggle source
# 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) click to toggle source
# 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