public List <TableDefinition> ReadTableDefinitions(List <string> dstoreEntries,
                                                           DataStoreType dstoreType,
                                                           DatabaseDefinition dbDef)
        {
            var tableList = new List <TableDefinition>();

            // find range of data lines for gien db
            var dbRange = extractDBRange(dstoreType.TypeName, dstoreEntries, dbDef);

            var tableFinder         = buildMdataRegex(mdataTable, "tableinfo");
            var tableInternalFinder = buildMdataRegex(mdataTableInternal, "tableinternal");
            var tableHistFinder     = buildMdataRegex(mdataTableHistory, "tablehistory");
            var tableSepFinder      = buildMdataRegex(mdataTableFieldSeparator, "tablesep");
            var tableTagFinder      = buildMdataRegex(mdataTableTags, "tabletags");

            // tabke key field finder
            var tableKeyFinder = buildMdataRegex(mdataTablePKField, "keyfieldname");
            // field and wire def finders
            var tableFieldDefsFinder = new Regex(mdataPrefix + mdataTablePKField
                                                 + "\\" + mdataValueOpen + ".*?" + "\\" + mdataValueClose
                                                 + "\\s(?<fielddefs>.*)");
            var tableFieldFinder = new Regex(mdataPrefix + "(?<fieldtype>.*?)"
                                             + "\\" + mdataValueOpen
                                             + "(?<fielddef>.*?)"
                                             + "\\" + mdataValueClose);

            foreach (string entryLine in dstoreEntries.GetRange(dbRange.First, dbRange.Count))
            {
                var match = tableFinder.Match(entryLine);
                if (match.Success)
                {
                    var tableinfo     = decodeKeyValue(match.Groups["tableinfo"].Value);
                    var internalMatch = tableInternalFinder.Match(entryLine);
                    var histMatch     = tableHistFinder.Match(entryLine);
                    var sepMatch      = tableSepFinder.Match(entryLine);
                    var tagsMatch     = tableTagFinder.Match(entryLine);

                    var fields = new List <Field>();
                    var wires  = new List <Wire>();

                    // find key field name
                    Field  keyField     = null;
                    var    keyMatch     = tableKeyFinder.Match(entryLine);
                    string keyFieldName = decodeValue(keyMatch.Groups["keyfieldname"].Value);

                    // find all defined fields
                    var fieldDefs = tableFieldDefsFinder.Match(entryLine).Groups["fielddefs"].Value;
                    foreach (Match fmatch in tableFieldFinder.Matches(fieldDefs))
                    {
                        Field newField = null;
                        Wire  newWire  = null;

                        var fieldType     = decodeValue(fmatch.Groups["fieldtype"].Value);
                        var fieldDefParts = decodeKeyValue(fmatch.Groups["fielddef"].Value);

                        switch (fieldType)
                        {
                        case mdataFieldBool:
                            newField = new BooleanField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldByte:
                            newField = new ByteField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldInteger:
                            newField = new IntegerField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldText:
                            newField = new TextField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldDate:
                            newField = new DateField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldTime:
                            newField = new TimeField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldTimeStamp:
                            newField = new TimeStampField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldTimeStampTZ:
                            newField = new TimeStampTZField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldDecimal:
                            newField = new DecimalField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldUUID:
                            newField = new UUIDField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldJSON:
                            newField = new JSONField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        case mdataFieldXML:
                            newField = new XMLField(name: fieldDefParts.Key, description: fieldDefParts.Value); break;

                        // if field type is a wire
                        case mdataTableFieldWire:
                            newWire = new Wire(fromFieldName: fieldDefParts.Key,
                                               toFieldName: fieldDefParts.Value);
                            break;

                        // default to generic if type is unknown
                        default:
                            newField = new Field(datatype: FieldType.Undefined, name: fieldDefParts.Key, description: fieldDefParts.Value); break;
                        }

                        if (newField != null)
                        {
                            if (fieldDefParts.Key == keyFieldName)
                            {
                                keyField = newField;
                            }

                            fields.Add(newField);
                        }

                        if (newWire != null)
                        {
                            wires.Add(newWire);
                        }
                    }

                    // TODO: implement nullable property read
                    // TODO: implement reaky @key (currently first field is considered key)

                    // create table def
                    tableList.Add(new TableDefinition(tableName: tableinfo.Key)
                    {
                        Fields          = fields,
                        Wires           = wires,
                        Description     = tableinfo.Value,
                        FieldDelimiter  = decodeValue(sepMatch.Groups["tablesep"].Value),
                        SupportsTags    = decodeValue(tagsMatch.Groups["tabletags"].Value) == mdataBoolTrue,
                        IsHidden        = decodeValue(internalMatch.Groups["tableinternal"].Value) == mdataBoolTrue,
                        SupportsHistory = decodeValue(histMatch.Groups["tablehistory"].Value) == mdataBoolTrue,
                    });
                }
            }

            return(tableList);
        }
        private EntryRange extractTableRange(string dstoreTypeName, List <string> dstoreEntries, DatabaseDefinition dbDef, TableDefinition tableDef)
        {
            var dbRange = extractDBRange(dstoreTypeName, dstoreEntries, dbDef);

            return(extractRange(dstoreEntries, mdataTable, tableDef.Name, keyvalue: true, relativeRange: dbRange));
        }
        // tables
        public EntryChangeRequest BuildTableEntry(List <string> dstoreEntries,
                                                  DataStoreType dstoreType,
                                                  DatabaseDefinition dbDef,
                                                  TableDefinition tableDef)
        {
            // build table title line
            var lineParts = new List <string>();

            lineParts.Add(medataLineStart);
            if (mdataTablePrefix != string.Empty)
            {
                lineParts.Add(mdataTablePrefix);
            }

            lineParts.Add(buildMdataKeyValueString(mdataTable, tableDef.Name, tableDef.Description));
            lineParts.Add(buildMdataString(mdataTableFieldSeparator, tableDef.FieldDelimiter));
            lineParts.Add(buildMdataString(mdataTableInternal, tableDef.IsHidden));
            lineParts.Add(buildMdataString(mdataTableTags, tableDef.SupportsTags));
            lineParts.Add(buildMdataString(mdataTableHistory, tableDef.SupportsHistory));
            lineParts.Add(buildMdataString(mdataTableEncapsulation, tableDef.EncapsulateValues));
            lineParts.Add(buildMdataString(mdataTableHeaders, tableDef.SupportsHeaders));

            // build table fields
            if (tableDef.Key != null)
            {
                lineParts.Add(buildMdataString(mdataTablePKField, tableDef.Key.Name, wrapInQuotes: false));
            }
            else
            {
                lineParts.Add(buildMdataString(mdataTablePKField, mdataNotSet));
            }

            foreach (Field field in tableDef.Fields)
            {
                string typeKeyName;
                switch (field.FieldType)
                {
                case FieldType.Boolean: typeKeyName = mdataFieldBool; break;

                case FieldType.Byte: typeKeyName = mdataFieldByte; break;

                case FieldType.Integer: typeKeyName = mdataFieldInteger; break;

                case FieldType.Text: typeKeyName = mdataFieldText; break;

                case FieldType.Date: typeKeyName = mdataFieldDate; break;

                case FieldType.Time: typeKeyName = mdataFieldTime; break;

                case FieldType.TimeStamp: typeKeyName = mdataFieldTimeStamp; break;

                case FieldType.TimeStampTZ: typeKeyName = mdataFieldTimeStampTZ; break;

                case FieldType.Decimal: typeKeyName = mdataFieldDecimal; break;

                case FieldType.UUID: typeKeyName = mdataFieldUUID; break;

                case FieldType.JSON: typeKeyName = mdataFieldJSON; break;

                case FieldType.XML: typeKeyName = mdataFieldXML; break;

                default:
                    typeKeyName = mdataFieldText; break;
                }

                // TODO: implement nullable property
                lineParts.Add(buildMdataKeyValueString(typeKeyName, field.Name, field.Description));
            }

            // build table wires
            foreach (Wire wire in tableDef.Wires)
            {
                lineParts.Add(buildMdataKeyValue(mdataTableFieldWire, wire.FromFieldName, wire.ToFieldName));
            }

            var dbRange = extractDBRange(dstoreType.TypeName, dstoreEntries, dbDef);

            return(new EntryChangeRequest(
                       EntryChangeType.Insert,
                       dbRange.Last + 1,
                       string.Join(mdataSeparator, lineParts)
                       ));
        }