Module: DataMetaDom::MySqlLexer

Defined in:
lib/dataMetaDom/mySql.rb

Overview

Definition for generating MySQL 5 artifacts such as schemas, select statements, ORM input files etc etc

TODO this isn’t a bad way, but beter use templating next time such as ERB.

For command line details either check the new method’s source or the README.rdoc file, the usage section.

Defined Under Namespace

Classes: SqlOutput

Constant Summary collapse

INT_TYPES =

Integer types

{2 => 'smallint', 4 => 'int', 8 => 'bigint'}
FLOAT_TYPES =

Float types

{4 => 'float', 8 => 'double'}
NOT_NULL =

Not null (required) wording per MySQL DDL syntax

' not null'
SQL_TYPES =

Mapping from DataMeta DOM standard types to correspondent MySQL types renderer lambdas.

{
        INT => lambda { |len, isReq|
            concreteType = INT_TYPES[len]
            raise "Invalid integer type length #{len} " unless concreteType
            "#{concreteType}#{isReq ? NOT_NULL : ''}"
        },
        DataMetaDom::FLOAT => lambda { |len, isReq|
            concreteType = FLOAT_TYPES[len]
            raise "Invalid integer type length #{len} " unless concreteType
            "#{concreteType}#{isReq ? NOT_NULL : ''}"
        },
        STRING => lambda { |len, isReq| "varchar(#{len})#{isReq ? NOT_NULL : ''}" },
        DATETIME => lambda { |len, isReq| "datetime#{isReq ? NOT_NULL : ''}" },
        RAW => lambda { |len, isReq| "varbinary(#{len})#{isReq ? NOT_NULL : ''}" },

        # Regarding the URL length:
        # https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2,083-characters-in-internet-explorer
        # https://tools.ietf.org/html/rfc7230#section-3.1.1
        # http://www.faqs.org/rfcs/rfc2616.html
        URL => lambda { |len, isReq| "varchar(2083)#{isReq ? NOT_NULL : ''}" },

        BOOL => lambda { |len, isReq| "bool#{isReq ? NOT_NULL : ''}" }
}

Instance Method Summary collapse

Instance Method Details

#autoGenClauseIfAny(record, field) ⇒ Object

Builds and returns an autoincrement clause if applicable, for the given record and the field.

If the field is the one and only identity on the record and if it is an integral type, returns the auto increment clause, otherwise returns and empty string.



145
146
147
148
# File 'lib/dataMetaDom/mySql.rb', line 145

def autoGenClauseIfAny(record, field)
    record.identity && record.identity.length == 1 && field.name == record.identity[0] &&
            field.dataType.type == DataMetaDom::INT ? ' AUTO_INCREMENT' : ''
end

#fkName(bareEntityName, index) ⇒ Object

Builds and returns the foreign key name for the given entity (Record) name and the counting number of these.

  • Parameters:

    • bareEntityName - the entity name without the namespace

    • index - an integer, an enumerated counting number, starting from one. For each subsequent FK this number is incremented.



190
191
192
# File 'lib/dataMetaDom/mySql.rb', line 190

def fkName(bareEntityName, index)
    "fk_#{bareEntityName}_#{index}"
end

#genDdl(parser, outDir) ⇒ Object

Generate the MySQL DDL from the given Model into the given output directory.

  • Parameters

    • parser - an instance of a Model

    • outDir - a String, the directory to generate the DDL into.



252
253
254
255
256
257
258
259
260
261
# File 'lib/dataMetaDom/mySql.rb', line 252

def genDdl(parser, outDir)
    out = SqlOutput.new(outDir)
    begin
        parser.records.each_key { |r|
            renderRecord(out, parser, r)
        }
    ensure
        out.close
    end
end

#renderField(createStatement, parser, record, fieldKey, isFirstField) ⇒ Object

Renders the given field into create statement.

  • Parameters:

    • createStatement - the create statement to append the field definition to.

    • parser - the instance of the Model

    • record - the instance of the Record to which the field belongs

    • fieldKey - the full name of the field to render turned into a symbol.

    • isFirstField - the boolean, true if the field is first in the create statement.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/dataMetaDom/mySql.rb', line 159

def renderField(createStatement, parser, record, fieldKey, isFirstField)
    field = record[fieldKey]
    ty = field.dataType
    stdRenderer = SQL_TYPES[ty.type]
    typeEnum = parser.enums[ty.type]
    typeRec = parser.records[ty.type]

    typeDef = if stdRenderer
                  stdRenderer.call ty.length, field.isRequired
              elsif typeEnum
                  "enum('#{typeEnum.values.join("','")}')"
              elsif typeRec
                  raise "Invalid ref to #{typeRec} - it has no singular ID" unless typeRec.identity.length == 1
                  idField = typeRec[typeRec.identity[0]]
                  idRenderer = SQL_TYPES[idField.dataType.type]
                  raise 'Only one-level prim type references only allowed in this version' unless idRenderer
                  idRenderer.call idField.dataType.length, field.isRequired
              else
                  raise ArgumentError, "Unsupported datatype #{ty}"
              end
    createStatement << ",\n" unless isFirstField
    createStatement << "\t#{field.name} #{typeDef}#{autoGenClauseIfAny(record, field)}"
end

#renderRecord(out, parser, recordKey) ⇒ Object

Render SQL record with for the given model into the given output.

  • Parameters

    • out - an instance of SqlOutput

    • parser - an instance of Model

    • recordKey - full name of the record datatype including namespeace if any turned into a symbol.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/dataMetaDom/mySql.rb', line 201

def renderRecord(out, parser, recordKey)
    record = parser.records[recordKey]
    ns, entityName = DataMetaDom.splitNameSpace record.name
    isFirstField = true
    out.drop.puts "\ndrop table if exists #{entityName};"
    fkNumber = 1 # to generate unique names that fit in 64 characters of identifier max length for MySQL
    record.refs.select { |r| r.type == Reference::RECORD }.each { |ref|
        ns, fromEntityBareName = DataMetaDom.splitNameSpace ref.fromEntity.name
        ns, toEntityBareName = DataMetaDom.splitNameSpace ref.toEntity.name
        out.couple.puts "alter table #{fromEntityBareName} add constraint #{fkName(fromEntityBareName, fkNumber)} "\
  " foreign key (#{ref.fromField.name}) references #{toEntityBareName}(#{ref.toFields.name});"
        out.uncouple.puts "alter table #{fromEntityBareName} drop foreign key #{fkName(fromEntityBareName, fkNumber)};"
        fkNumber += 1
    }
    ids = record.identity ? record.identity.args : []
    createStatement = "create table #{entityName} (\n"
    fieldKeys = [] << ids.map { |i| i.to_s }.map { |i| i.to_sym } \
   << record.fields.keys.select { |k| !ids.include?(k) }.map { |k| k.to_s }.map { |k| k.to_sym }

    fieldKeys.flatten.each { |f|
        renderField(createStatement, parser, record, f, isFirstField)
        isFirstField = false
    }
    if record.identity && record.identity.length > 0
        createStatement << ",\n\tprimary key(#{ids.join(', ')})"
    end
    unless record.uniques.empty?
        uqNumber = 1
        record.uniques.each_value { |uq|
            createStatement << ",\n\tunique uq_#{entityName}_#{uqNumber}(#{uq.args.join(', ')})"
            uqNumber += 1 # to generate unique names that fit in 64 characters of identifier max length for MySQL
        }
    end
    unless record.indexes.empty?
        ixNumber = 1
        record.indexes.each_value { |ix|
            createStatement << ",\n\tindex ix_#{entityName}_#{ixNumber}(#{ix.args.join(', ')})"
            ixNumber += 1 # to generate unique names that fit in 64 characters of identifier max length for MySQL
        }
    end
    createStatement << "\n) Engine=InnoDB;\n\n" # MyISAM, the default engine does not support FKs

    out.create.puts createStatement
end