class ActiveFacts::Generators::Doc::CWM

Constants

MM

Public Class Methods

compatibility() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 26
def self.compatibility
  [1, %i{relational}]   # one relational composition
end
new(constellation, composition, options = {}) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 30
def initialize constellation, composition, options = {}
  @constellation = constellation
  @composition = composition
  @options = options
  @underscore = options.has_key?("underscore") ? (options['underscore'] || '_') : ''

  @vocabulary = @composition.constellation.Vocabulary.values[0]      # REVISIT when importing from other vocabularies
end
options() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 20
def self.options
  {
    underscore: [String, "Use 'str' instead of underscore between words in table names"]
  }
end

Public Instance Methods

boolean_type() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 336
def boolean_type
  'boolean'
end
check_clause(column_name, value_constraint) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 425
def check_clause column_name, value_constraint
  " CHECK(" +
    value_constraint.all_allowed_range_sorted.map do |ar|
      vr = ar.value_range
      min = vr.minimum_bound
      max = vr.maximum_bound
      if (min && max && max.value.literal == min.value.literal)
        "#{column_name} = #{sql_value(min.value)}"
      else
        inequalities = [
          min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
          max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
        ].compact
        inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
      end
    end*" OR " +
  ")"
end
column_name(component) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 85
def column_name component
  component.column_name.capcase
end
column_name_max() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 61
def column_name_max
  40
end
create_data_type(type_name, type_num, type_params) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 246
def create_data_type(type_name, type_num, type_params)
  type_ns = rawnsdef

  cwm_data_type =
    "<CWMRDB:SQLSimpleType xmi.id=\"#{type_ns}\" name=\"#{type_name}\" visibility=\"public\" typeNumber=\"#{type_num}\" #{type_params}/>"

  @datatypes << cwm_data_type
  type_ns
end
data_type_context() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 39
def data_type_context
  @data_type_context ||= CWMDataTypeContext.new
end
escape(s, max = table_name_max) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 358
def escape s, max = table_name_max
  # Escape SQL keywords and non-identifiers
  if s.size > max
    excess = s[max..-1]
    s = s[0...max-(excess.size/8)] +
      Digest::SHA1.hexdigest(excess)[0...excess.size/8]
  end

  if s =~ /[^A-Za-z0-9_]/ || is_reserved_word(s)
    "[#{s}]"
  else
    s
  end
end
generate() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 43
def generate
  # @tables_emitted = {}
  @ns = 0
  @datatypes = Array.new

  trace.enable 'cwm'

  model_ns, schema_ns = populate_namespace_ids

  generate_header +
  generate_content(model_ns, schema_ns) +
  generate_footer
end
generate_catalog(depth, model_ns, schema_ns) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 161
def generate_catalog(depth, model_ns, schema_ns)
  catalog_start =
    indent(depth, "<CWMRDB:Catalog xmi.id=\"#{model_ns}\" name=\"Model\" visibility=\"public\">") +
    indent(depth, "  <CWM:Namespace.ownedElement>") +
    indent(depth, "    <CWMRDB:Schema xmi.id=\"#{schema_ns}\" name=\"Schema\" visibility=\"public\" namespace=\"#{model_ns}\">") +
    indent(depth, "      <CWM:Namespace.ownedElement>")

  catalog_body =
    @composition.
    all_composite.
    sort_by{|composite| composite.mapping.name}.
    map{|composite| generate_table(depth+4, schema_ns, composite)}*"\n" + "\n"

  catalog_end =
    indent(depth, "      </CWM:Namespace.ownedElement>") +
    indent(depth, "    </CWMRDB:Schema>") +
    indent(depth, "  </CWM:Namespace.ownedElement>") +
    indent(depth, "</CWMRDB:Catalog>")

  catalog_start + catalog_body + catalog_end
end
generate_column(depth, table_ns, column) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 227
def generate_column(depth, table_ns, column)
  name = safe_column_name(column)

  is_nullable = column.path_mandatory ? "columnNoNulls" : "columnNullable"
  constraints = column.all_leaf_constraint

  type_name, options = column.data_type(data_type_context)
  options ||= {}
  length = options[:length]

  type_name, type_num = normalise_type_cwm(type_name, length)
  column_params = ""
  type_params = ""

  type_ns = create_data_type(type_name, type_num, type_params)

  indent(depth, "<CWMRDB:Column xmi.id=\"#{column.xmiid}\" name=\"#{name}\" isNullable=\"#{is_nullable}\" visibility=\"public\" type=\"#{type_ns}\" owner=\"#{table_ns}\" #{column_params}/>")
end
generate_content(model_ns, schema_ns) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 150
def generate_content(model_ns, schema_ns)
  "  <XMI.content>\n" +
  generate_catalog(2, model_ns, schema_ns) +
  generate_data_types(2) +
  "  </XMI.content>\n"
end
generate_data_types(depth) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 183
def generate_data_types(depth)
  @datatypes.map do | dt |
    indent(depth, dt)
  end * ""
end
generate_foreign_key(depth, table_ns, fk) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 306
def generate_foreign_key(depth, table_ns, fk)
  key_ns = fk.xmiid

  if fk.all_foreign_key_field.size == 1
    fkf = fk.all_foreign_key_field[0]
    ixf = fk.all_index_field[0]
    indent(depth, "<CWMRDB:ForeignKey xmi.id=\"#{key_ns}\" name=\"R#{key_ns}\" visibility=\"public\" namespace=\"#{table_ns}\" feature=\"#{fkf.component.xmiid}\" uniqueKey=\"#{ixf.component.index_xmiid}\" />")
  else
    indent(depth, "<CWMRDB:ForeignKey xmi.id=\"#{key_ns}\" name=\"R#{key_ns}\" visibility=\"public\" namespace=\"#{table_ns}\">") +
    indent(depth, "  <CWM:KeyRelationship.feature>") +
    begin
      out = ""
      for i in 0..(fk.all_foreign_key_field.size - 1)
        fkf = fk.all_foreign_key_field[i]
        ixf = fk.all_index_field[i]
        out += indent(depth, "    <CWM:StructuralFeature xmi.idref=\"#{fkf.component.xmiid}\" uniqueKey=\"#{ixf.component.index_xmiid}\" />")
      end
      out
    end +
    indent(depth, "  </CWM:KeyRelationship.feature>") +
    indent(depth, "</CWMRDB:ForeignKey>")
  end
  # fk.all_foreign_key_field.map{|fkf| safe_column_name fkf.component}*", " +
  # ") REFERENCES #{safe_table_name fk.composite} (" +
  # fk.all_index_field.map{|ixf| safe_column_name ixf.component}*", " +

  # indent(depth, "<CWMRDB:ForeignKey xmi.id=\"#{key_ns}\" name=\"R#{key_ns}\" visibility=\"public\" namespace=\"#{ns}\" feature=\"_41\" uniqueKey=\"_48\" deleteRule=\"importedKeyRestrict\"  updateRule=\"importedKeyRestrict\"/>")

end
generate_header() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 136
def generate_header
  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
  "<!DOCTYPE XMI SYSTEM \"CWM-1.1.dtd\">\n" +
  "\n" +
  "<XMI xmlns:CWM=\"org.omg.CWM1.1\" xmlns:CWMRDB=\"org.omg.CWM1.1/Relational\" xmi.version=\"1.1\">\n" +
  "  <XMI.header>\n" +
  "    <XMI.documentation>\n" +
  "      <XMI.exporter>ActiveFacts</XMI.exporter>\n" +
  "      <XMI.exporterVersion>0.1</XMI.exporterVersion>\n" +
  "    </XMI.documentation>\n" +
  "    <XMI.metamodel xmi.name=\"CWM\" xmi.version=\"1.1\" />" +
  "  </XMI.header>\n"
end
generate_index(depth, table_ns, index, table_name, all_fks_as_target) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 256
def generate_index(depth, table_ns, index, table_name, all_fks_as_target)
  key_ns = index.xmiid

  nullable_columns =
    index.all_index_field.select do |ixf|
      !ixf.component.path_mandatory
    end
  contains_nullable_columns = nullable_columns.size > 0

  primary = index.composite_as_primary_index && !contains_nullable_columns
  column_ids =
    index.all_index_field.map do |ixf|
      ixf.component.xmiid
    end
  # clustering =
  #   (index.composite_as_primary_index ? ' CLUSTERED' : ' NONCLUSTERED')

  key_type = primary ? 'CWMRDB:PrimaryKey' : 'CWM:UniqueKey'

  # find target foreign keys for this index
  fks_as_target = all_fks_as_target

  if column_ids.count == 1 && fks_as_target.count == 0
    colid = column_ids[0]
    indent(depth, "<#{key_type} xmi.id=\"#{key_ns}\" name=\"XPK#{table_name}\" visibility=\"public\" namespace=\"#{table_ns}\" feature=\"#{colid}\"/>")
  else
    if column_ids.count == 1
      colid = column_ids[0]
      indent(depth, "<#{key_type} xmi.id=\"#{key_ns}\" name=\"XPK#{table_name}\" visibility=\"public\" namespace=\"#{table_ns}\" feature=\"#{colid}\">")
    else
      indent(depth, "<#{key_type} xmi.id=\"#{key_ns}\" name=\"XPK#{table_name}\" visibility=\"public\" namespace=\"#{table_ns}\">") +
      indent(depth, "  <CWM:UniqueKey.feature>") +
      column_ids.map do |id|
        indent(depth, "    <CWM:StructuralFeature xmi.idref=\"#{id}\"/>")
      end * "" +
      indent(depth, "  </CWM:UniqueKey.feature>")
    end +
    if fks_as_target.count > 0
      indent(depth, "<CWM:UniqueKey.keyRelationship>") +
      fks_as_target.map do |fk|
        indent(depth, " <CWM:KeyRelationship xmi.idref=\"#{fk.xmiid}\"/>")
      end * "" +
      indent(depth, "</CWM:UniqueKey.keyRelationship>")
    else
      ""
    end +
    indent(depth, "</#{key_type}>")
  end
end
generate_table(depth, schema_ns, table) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 189
def generate_table(depth, schema_ns, table)
  name = table_name(table)
  delayed_indices = []

  table_start =
    indent(depth, "<CWMRDB:Table xmi.id=\"#{table.xmiid}\" name=\"#{name}\" isSystem=\"false\" isTemporary=\"false\" visibility=\"public\" namespace=\"#{schema_ns}\">")

  table_columns =
    indent(depth, "  <CWM:Classifier.feature>") +
    (table.mapping.all_leaf.flat_map.sort_by{|c| column_name(c)}.map do |leaf|
        # Absorbed empty subtypes appear as leaves
        next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)

        generate_column(depth+2, table.xmiid, leaf)
      end
    ).compact.flat_map{|f| "#{f}" } * "" +
    indent(depth, "  </CWM:Classifier.feature>")

  table_keys =
    indent(depth, "  <CWM:Namespace.ownedElement>") +
      (table.all_index.sort_by do |idx|
        idx.all_index_field.map { |ixf| ixf.component.xmiid }
      end.map do |index|
        generate_index(depth+2, table.xmiid, index, name, table.all_foreign_key_as_target_composite)
      end
    ) * "" +
    (table.all_foreign_key_as_source_composite.sort_by{|fk| [fk.source_composite.mapping.name, fk.mapping.inspect] }.map do |fk|
        generate_foreign_key(depth+2, table.xmiid, fk)
      end
    ) * "" +
    indent(depth, "  </CWM:Namespace.ownedElement>")

  table_end =
    indent(depth, "</CWMRDB:Table>")

  table_start + table_columns + table_keys + table_end
end
indent(depth, str) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 89
def indent depth, str
  "  " * depth + str + "\n"
end
index_name_max() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 65
def index_name_max
  60
end
is_reserved_word(w) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 349
def is_reserved_word w
  @reserved_word_hash ||=
    reserved_words.inject({}) do |h,w|
      h[w] = true
      h
    end
  @reserved_word_hash[w.upcase]
end
normalise_type_cwm(type_name, length) click to toggle source

Return CWM type, typenum for the passed base type

# File lib/activefacts/generator/doc/cwm.rb, line 374
def normalise_type_cwm(type_name, length)
  type = MM::DataType.intrinsic_type(type_name)

  case type
  when MM::DataType::TYPE_Boolean;  ['boolean', 16]
  when MM::DataType::TYPE_Integer
    case type_name
    when /^Auto ?Counter$/i
      ['int', 4]
    when /([a-z ]|\b)Tiny([a-z ]|\b)/i
      ['tinyint', -6]
    when /([a-z ]|\b)Small([a-z ]|\b)/i,
      /([a-z ]|\b)Short([a-z ]|\b)/i
      ['smallint', 5]
    when /([a-z ]|\b)Big(INT)?([a-z ]|\b)/i
      ['bigint', -5]
    else
      ['int', 4]
    end
  when MM::DataType::TYPE_Real;
    ['real', 7, data_type_context.default_length(type, type_name)]
  when MM::DataType::TYPE_Decimal;  ['decimal', 3]
  when MM::DataType::TYPE_Money;    ['decimal', 3]
  when MM::DataType::TYPE_Char;     ['char', 12, length]
  when MM::DataType::TYPE_String;   ['varchar', 12, length]
  when MM::DataType::TYPE_Text;     ['text', 2005, length || 'MAX']
  when MM::DataType::TYPE_Date;     ['date', 91]
  when MM::DataType::TYPE_Time;     ['time', 92]
  when MM::DataType::TYPE_DateTime; ['datetime', 93]
  when MM::DataType::TYPE_Timestamp;['timestamp', -3]
  when MM::DataType::TYPE_Binary;
    length ||= 16 if type_name =~ /^(guid|uuid)$/i
    if length
      ['BINARY', -2]
    else
      ['VARBINARY', -2]
    end
  else
    ['int', 4]
  end
end
nsdef(obj, pref = nil) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 98
def nsdef obj, pref = nil
  if obj.xmiid == nil
    obj.xmiid = "#{pref}#{rawnsdef}"
  end
  obj.xmiid
end
populate_namespace_ids() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 105
def populate_namespace_ids
  model_ns = rawnsdef
  schema_ns = rawnsdef

  @composition.
  all_composite.
  sort_by{|composite| composite.mapping.name}.
  map{|composite| populate_table_ids(composite)}

  [model_ns, schema_ns]
end
populate_table_ids(table) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 117
def populate_table_ids(table)
  tname = table_name(table)
  nsdef(table)
  table.mapping.all_leaf.flat_map.sort_by{|c| column_name(c)}.map do |leaf|
    # Absorbed empty subtypes appear as leaves
    next if leaf.is_a?(MM::Absorption) && leaf.parent_role.fact_type.is_a?(MM::TypeInheritance)
    nsdef(leaf)
  end
  table.all_index.sort_by do |idx|
    idx.all_index_field.map { |ixf| ixf.component.xmiid }
  end.map do |index|
    nsdef(index)
    index.all_index_field.map{|idf| idf.component.index_xmiid = index.xmiid}
  end
  table.all_foreign_key_as_source_composite.sort_by{|fk| [fk.source_composite.mapping.name, fk.mapping.inspect] }.map do |fk|
    nsdef(fk)
  end
end
rawnsdef() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 93
def rawnsdef
  @ns += 1
  "_#{@ns}"
end
reserved_words() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 345
def reserved_words
  @reserved_words ||= %w{ }
end
safe_column_name(component) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 77
def safe_column_name component
  escape(column_name(component), column_name_max)
end
safe_table_name(composite) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 73
def safe_table_name composite
  escape(table_name(composite), table_name_max)
end
schema_name_max() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 69
def schema_name_max
  60
end
sql_string(str) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 421
def sql_string(str)
  "'" + str.gsub(/'/,"''") + "'"
end
sql_value(value) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 417
def sql_value(value)
  value.is_literal_string ? sql_string(value.literal) : value.literal
end
surrogate_type() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 340
def surrogate_type
  'bigint'
end
table_name(composite) click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 81
def table_name composite
  composite.mapping.name.gsub(' ', @underscore)
end
table_name_max() click to toggle source
# File lib/activefacts/generator/doc/cwm.rb, line 57
def table_name_max
  60
end