public static bool ColsAreCompatible(Column colToUpdate, Column colSource)
        {
            bool bCompatible = false;

            if (colToUpdate.colType.Equals(COLUMN_TYPES.AUTOINCREMENT))
            {
                bCompatible = false;    // hard stop.  Can't update autoincrement fields.
            }
            else if (colToUpdate.colType.Equals(colSource.colType))
            {
                bCompatible = true;
            }
            else
            {
                // TODO: Are there other sets we want to consider equivalent?
                Queue<COLUMN_TYPES[]> qLinkedTypes = new Queue<COLUMN_TYPES[]>();
                COLUMN_TYPES[] sharedIntTypes = { COLUMN_TYPES.INT, COLUMN_TYPES.AUTOINCREMENT, COLUMN_TYPES.TINYINT };
                qLinkedTypes.Enqueue(sharedIntTypes);

                foreach (COLUMN_TYPES[] relatedColTypes in qLinkedTypes)
                {
                    if (sharedIntTypes.Contains(colToUpdate.colType) && sharedIntTypes.Contains(colSource.colType))
                    {
                        bCompatible = true;
                        break;
                    }
                }
            }
            return bCompatible && colToUpdate.intColLength >= colSource.intColLength;
        }
        public CompositeColumnValueModifier(string strValue, Column column, bool isAdditionModifierNotSubtraction)
        {
            if (string.IsNullOrWhiteSpace(strValue))
            {
                if (null == column)
                {
                    throw new Exception("Update modifiers must have a value or column to initialize.");
                }

                this.column = column;
            }
            else
            {
                this.strValue = strValue;
            }
            this.isAdditionModifierNotSubtraction = isAdditionModifierNotSubtraction;
        }
Esempio n. 3
0
        public static BaseSerializer routeMe(Column colToRoute)
        {
            BaseSerializer serializer = null;

            switch (colToRoute.colType)
            {
                case COLUMN_TYPES.INT:
                case COLUMN_TYPES.TINYINT:
                case COLUMN_TYPES.AUTOINCREMENT:
                    serializer = new IntSerializer(colToRoute);
                    break;

                case COLUMN_TYPES.CHAR:
                    serializer = new CharSerializer(colToRoute);
                    break;

                // For now, I'm going to cheese out and treat these both
                // as decimal.  As Skeet says [1], they're both *floating point*
                // representations, even if we don't normally think of FLOATS
                // the same as we do decimals.
                // [1] http://tinyurl.com/mzu6t4j
                // TODO: Create a real float type for huge numbers.
                case COLUMN_TYPES.FLOAT:
                case COLUMN_TYPES.DECIMAL:
                    serializer = new DecimalSerializer(colToRoute);
                    break;

                // TODO: Create a real BIT column type with serializer.
                case COLUMN_TYPES.BIT:
                    serializer = new IntSerializer(colToRoute);
                    break;

                case COLUMN_TYPES.DATETIME:
                    serializer = new DateSerializer(colToRoute);
                    break;

                default:
                    throw new Exception("Illegal/Unimplemented column type: " + colToRoute.colType);
            }

            return serializer;
        }
Esempio n. 4
0
 // inherited Column colRelated
 // Apparently I need to "manually chain" constructors?
 // http://stackoverflow.com/a/7230589/1028230
 public DateSerializer(Column colToUseAsBase)
     : base(colToUseAsBase)
 {
     this.colRelated = colToUseAsBase;
 }
Esempio n. 5
0
 public BaseSerializer(Column colToUseAsBase)
 {
     this.colRelated = colRelated;
 }
Esempio n. 6
0
        private void _prepareTableFromFile()
        {
            if (_strTableName.Equals("") || _strTableParentFolder.Equals(""))
            {
                throw new Exception("Attempted to prepare a table from a manager that has not been initialized.");
            }
            else
            {
                FileInfo info = new FileInfo(this.strTableFileLoc);
                // this is really the "come on, stop looking already" breakpoint.
                // TODO: Consider something less than 9,223,372,036,854,775,807 / 2.
                // Though, admittedly, that's a random thing to try and describe as
                // an absolute ceiling.  (Divided by 2 b/c there are always at least
                // two lines of metadata at the start of a mdbf file.)
                long lngStopTryingAt = (info.Length / 2) + 1; // TODO: Laziness alert with the +1.
                if (MainClass.bDebug) Console.WriteLine("Preparing table from: " + info.FullName);

                int intPullSize = 500;
                int intLineLength = -1;     // we can't know until we've actually found the first 0x11 0x11 combo.
                int intStartingByte = 0;    // I'm currently just starting at 0, reading ~500 bytes from a stream, looking for 0x11, 0x11, then,
                // if I haven't found it yet, scrolling forward to read approximately the next 500, ad infinitum
                // This is, admittedly, *NOT* the best way to accomplish reading far enough into the stream to find
                // the end of the metadata lines.
                bool bFoundLineEnd = false;
                while (!bFoundLineEnd)
                {
                    FileStream fs = File.Open(this.strTableFileLoc, FileMode.Open);
                    byte[] abytFirstLine = Utils.ReadToLength(fs, intPullSize + intStartingByte);
                    fs.Close(); // TODO: Keep open until you're done; closing now to avoid leaving it open when I break/stop when debugging

                    // Now see if we've gotten to the first 0x11, 0x11 line end.
                    // Note: The real disadvantage of this routine is if a field length
                    // is 4369 (decimal), which is 0x1111.  Ooops!  So 4369 is out as a length.
                    // (as is 69904-69919, etc etc, but let's pretend nobody's doing something
                    // that long for now. I mean, heck, 4369 is ludicrous enough).
                    for (int i = intStartingByte; i < abytFirstLine.Length - 1; i++)
                    {
                        if (0x11 == abytFirstLine[i] && 0x11 == abytFirstLine[i + 1])
                        {
                            bFoundLineEnd = true;
                            intLineLength = i + 2;    // add both 0x11s (remembering array is 0-based)
                            break;
                        }
                    }

                    // see if we found the end of the first line.
                    if (!bFoundLineEnd)
                    {
                        if (abytFirstLine.Length >= lngStopTryingAt)
                        {
                            throw new ImproperDatabaseFileException("Improperly formatted file, or file is not a database file: " + info.FullName);
                        }
                        intStartingByte += (intPullSize - 2);       // so we're just re-evaling /some/ of the bytes; no big loss.
                        if (0 == abytFirstLine.Length)
                        {           // we're out of bytes.
                            break;
                        }
                    }
                    else
                    {
                        // Start reading out column types and lengths

                        //=====================================
                        // Read out the column names.
                        //=====================================
                        // NOTES:
                        // 1.) Remember the Naming Kludge.  The length of the field has been linked to the length
                        // of the column name just to keep spacing in the table simpler (and stopping us from having
                        // table metadata tables.  So there are essentially two types of names.
                        //      a.) Names that are shorter than the length of the fields.
                        //              eg, "spam CHAR(20)"
                        //              20 > "spam".length, so we have the entire name in the name row.
                        //              0x73 0x70 0x61 0x6d 0x00 0x00 0x00... 0x11
                        //              s    p    a    m    zero zero zero... end
                        //
                        //      b.) Names that are as long or longer than their column length.
                        //              eg, "firstName CHAR(6)"
                        //              6 < "firstName".length
                        //              0x66 0x69 0x72 0x73 0x74 0x4e 0x11
                        //              f    i    r    s    t    N    end
                        //  Remember that each column ends with a 0x11 marker.  Excepting the first two metadata and
                        //  name rows, rows can contain 0x11 as well.
                        //
                        //  I'm horribly tempted to not use ASCII and to derive a more efficient encoding scheme to store names,
                        //  since we're only using [A-Za-z0-9] and $, #, _ -- 65 chars
                        //  http://stackoverflow.com/questions/954884/what-special-characters-are-allowed-in-t-sql-column-name
                        //  Unfortunately eight bits isn't enough to capture two of those.  65 still needs...
                        //  0 1 0 0 0 0 0 1
                        //  It'd have to fit in one nibble (four bits (16 options)) to do anything cool *and simple*.
                        //
                        // 2.) There are a couple of ways of reading these lines that would be more complicated but also more efficient.
                        // One would be to see if our abytFirstLine is big enough for two lines of info and use two ArraySegments.
                        // Or, if it's not long enough, I could make one read from 0 to 2*intLineLength, cut back into the same code,
                        // and segement that.
                        //
                        // I'm going to painfully and stodgily read in the second line from the original stream.
                        FileStream fs2 = File.Open(this.strTableFileLoc, FileMode.Open);
                        byte[] abytSecondLine = Utils.ReadToLength(fs2, intLineLength, intLineLength);   // the reader is zero-based, so start at the exact length position.
                        fs2.Close();    // TODO: Same thing as above -- keep open or, better yet, read from previously opened fs.

                        int j = 1;  // use 1 for j initially so we skip the line's initial 0x11
                        Queue<Column> qColumns = new Queue<Column>();
                        while (j < intLineLength)
                        {
                            Column column = new Column();
                            column.strColName = "";

                            // I think the for loop with breaks is more readable, so I'm going to avoid while (which is what I used initially)
                            // though it might make more sense to while loop until 0x00 or 0x11, and if it doesn't work correctly, the thrown exception
                            // for overshooting the length makes some sense, as the row was misconstructed.
                            int k;  // scoped to check it later.
                            for (k = j; k < intLineLength; k++)   // use 1 for k so we skip the line's initial 0x11
                            {
                                if (0x00 == abytSecondLine[k] || 0x11 == abytSecondLine[k])
                                    break;  // end of the column information
                                else
                                    column.strColName += (char)abytSecondLine[k];
                            }
                            column.isFuzzyName = 0x00 != abytSecondLine[k]; // if we were forced to end early (0x11 "end of col", not 0x00 "end of name"), we've got a fuzzy name.  See above.

                            column.intColStart = j; // start is at the first byte of the entry; this col starts on the jth bit of each row.
                            column.colType = (COLUMN_TYPES)abytFirstLine[j++];

                            if (Column.IsSingleByteType(column.colType))
                            {
                                // handle one byte col types
                                column.intColLength = 1;
                            }
                            else
                            {
                                Stack<byte> stackLength = new Stack<byte>();
                                while (abytFirstLine[j] != 0x00 && abytSecondLine[j] != 0x11)
                                {
                                    stackLength.Push(abytFirstLine[j++]);
                                }

                                if (COLUMN_TYPES.AUTOINCREMENT == column.colType)
                                {
                                    // We're using the intColLength to store the last autoincrement
                                    // value in this column type.
                                    column.intColLength = 4;    // NOTE: Changing from INT(4) will bork things.
                                    column.intAutoIncrementCount = Utils.ByteArrayToInt(stackLength.ToArray());
                                }
                                else
                                {
                                    column.intColLength = Utils.ByteArrayToInt(stackLength.ToArray());
                                }
                            }

                            // Roll to the end of the column's space.
                            while (abytSecondLine[j] != 0x11)
                            {
                                j++;
                            }

                            column.intColIndex = qColumns.Count;    // 0-based index of this column, if they're taken in order.
                            qColumns.Enqueue(column);

                            if (0x11 == abytFirstLine[j + 1])
                            {
                                break;  // if we found two 0x11s in a row, stop.  End of the row.
                            }
                            else
                            {
                                j++; // skip the col end 0x11 and get ready for the next column
                            }

                        }   // loop back to get next -- while (j < intLineLength)

                        _columns = qColumns.ToArray();

                        // +1 once for the check, above for the second 0x11 at abytFirstLine[j+1],
                        // then another because we want length, not 0-based byte number in the array.
                        // Note that this should include all the 0x11 overhead.
                        _intRowLength = j + 1 + 1;

                    }   // eo bFoundLineEnd == true logic.
                }   // eo while (!bFoundLineEnd)
            }   // eo else for if (_strTableName.Equals("") || _strTableParentFolder.Equals("")) // "initialization check"
        }
        // TODO: For now, we're stogily assuming `val[whitespace][plus or minus][whitespace][value]` etc.
        // TODO: Even though we're allowing multiple column names and values, this is still pretty naive,
        //      as we're not handling parentheses or order of operations at all.
        public static byte[] ConstructValue(Column colOutput, string strClause, byte[] abytRowOfValues, TableContext table)
        {
            string strOrigClause = strClause;
            strClause = "+ " + strClause;

            string[] astrTokens = strClause.Split();
            if (0 != astrTokens.Length % 2)
            {
                throw new Exception("Illegal update clause (value-operator count mismatch): " + strOrigClause);
            }

            Queue<CompositeColumnValueModifier> qModifiers = new Queue<CompositeColumnValueModifier>();

            for (int i = 0; i < astrTokens.Length; i = i + 2)
            {
                qModifiers.Enqueue(
                    new CompositeColumnValueModifier(
                        astrTokens[i + 1].IsNumeric() ? astrTokens[i + 1] : string.Empty,
                        astrTokens[i + 1].IsNumeric() ? null : table.getColumnByName(astrTokens[i + 1]),
                        !astrTokens[i].Equals("-")
                    )
                );
            }

            // I realize I could've done this in the loop where I construct
            // the UpdateModifiers, but this feels a little cleaner.
            byte[] abytResult = new byte[colOutput.intColLength];
            BaseSerializer outputSerializer = Router.routeMe(colOutput);
            foreach (CompositeColumnValueModifier modifier in qModifiers)
            {
                if (modifier.isValueNotColumn)
                {
                    abytResult = outputSerializer.addRawToStringRepresentation(abytResult, modifier.strValue, !modifier.isAdditionModifierNotSubtraction);
                }
                else
                {
                    if (colOutput.intColLength < modifier.column.intColLength || !ColsAreCompatible(colOutput, modifier.column))
                    {
                        throw new Exception("Value aggregation attempted to aggregate values that were potentially too large or with columns of incompatible types.");
                    }
                    byte[] abytValToAdd = new byte[modifier.column.intColLength];
                    Array.Copy(abytRowOfValues, modifier.column.intColStart, abytValToAdd, 0, modifier.column.intColLength);

                    abytResult = outputSerializer.addRawToRaw(abytResult, abytValToAdd, !modifier.isAdditionModifierNotSubtraction);
                }
            }

            return abytResult;
        }