qcubed/framework

View on GitHub
includes/database/QMySqliDatabase.class.php

Summary

Maintainability
F
2 wks
Test Coverage
<?php

if (!defined('MYSQLI_ON_UPDATE_NOW_FLAG')) {
    define ('MYSQLI_ON_UPDATE_NOW_FLAG', 8192);
}
    /**
     *
     * @package DatabaseAdapters
     */
    class QMySqliDatabase extends QDatabaseBase {
        const Adapter = 'MySql Improved Database Adapter for MySQL 4';

        protected $objMySqli;

        protected $strEscapeIdentifierBegin = '`';
        protected $strEscapeIdentifierEnd = '`';

        public function SqlLimitVariablePrefix($strLimitInfo) {
            // MySQL uses Limit by Suffixes (via a LIMIT clause)

            // If requested, use SQL_CALC_FOUND_ROWS directive to utilize GetFoundRows() method
            if (array_key_exists('usefoundrows', $this->objConfigArray) && $this->objConfigArray['usefoundrows'])
                return 'SQL_CALC_FOUND_ROWS';

            return null;
        }

        public function SqlLimitVariableSuffix($strLimitInfo) {
            // Setup limit suffix (if applicable) via a LIMIT clause 
            if (strlen($strLimitInfo)) {
                if (strpos($strLimitInfo, ';') !== false)
                    throw new Exception('Invalid Semicolon in LIMIT Info');
                if (strpos($strLimitInfo, '`') !== false)
                    throw new Exception('Invalid Backtick in LIMIT Info');
                return "LIMIT $strLimitInfo";
            }

            return null;
        }

        public function SqlSortByVariable($strSortByInfo) {
            // Setup sorting information (if applicable) via a ORDER BY clause
            if (strlen($strSortByInfo)) {
                if (strpos($strSortByInfo, ';') !== false)
                    throw new Exception('Invalid Semicolon in ORDER BY Info');
                if (strpos($strSortByInfo, '`') !== false)
                    throw new Exception('Invalid Backtick in ORDER BY Info');

                return "ORDER BY $strSortByInfo";
            }
            
            return null;
        }

        public function InsertOrUpdate($strTable, $mixColumnsAndValuesArray, $strPKNames = null) {
            $strEscapedArray = $this->EscapeIdentifiersAndValues($mixColumnsAndValuesArray);
            $strUpdateStatement = '';
            foreach ($strEscapedArray as $strColumn => $strValue) {
                if ($strUpdateStatement) $strUpdateStatement .= ', ';
                $strUpdateStatement .= $strColumn . ' = ' . $strValue;
            }
            $strSql = sprintf('INSERT INTO %s%s%s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s',
                $this->EscapeIdentifierBegin, $strTable, $this->EscapeIdentifierEnd,
                implode(', ', array_keys($strEscapedArray)),
                implode(', ', array_values($strEscapedArray)),
                $strUpdateStatement
            );
            $this->ExecuteNonQuery($strSql);
        }

        public function Connect() {
            // Connect to the Database Server
            $this->objMySqli = new MySqli($this->Server, $this->Username, $this->Password, $this->Database, $this->Port);

            if (!$this->objMySqli)
                throw new QMySqliDatabaseException("Unable to connect to Database", -1, null);
            
            if ($this->objMySqli->error)
                throw new QMySqliDatabaseException($this->objMySqli->error, $this->objMySqli->errno, null);

            // Update "Connected" Flag
            $this->blnConnectedFlag = true;

            // Set to AutoCommit
            $this->NonQuery('SET AUTOCOMMIT=1;');

            // Set NAMES (if applicable)
            if (array_key_exists('encoding', $this->objConfigArray))
                $this->NonQuery('SET NAMES ' . $this->objConfigArray['encoding'] . ';');
        }

        public function __get($strName) {
            switch ($strName) {
                case 'AffectedRows':
                    return $this->objMySqli->affected_rows;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        protected function ExecuteQuery($strQuery) {
            // Perform the Query
            $objResult = $this->objMySqli->query($strQuery);
            if ($this->objMySqli->error)
                throw new QMySqliDatabaseException($this->objMySqli->error, $this->objMySqli->errno, $strQuery);

            // Return the Result
            $objMySqliDatabaseResult = new QMySqliDatabaseResult($objResult, $this);
            return $objMySqliDatabaseResult;
        }

        protected function ExecuteNonQuery($strNonQuery) {
            // Perform the Query
            $this->objMySqli->query($strNonQuery);
            if ($this->objMySqli->error)
                throw new QMySqliDatabaseException($this->objMySqli->error, $this->objMySqli->errno, $strNonQuery);
        }
        
        public function GetTables() {
            // Use the MySQL "SHOW TABLES" functionality to get a list of all the tables in this database
            $objResult = $this->Query("SHOW TABLES");
            $strToReturn = array();
            while ($strRowArray = $objResult->FetchRow())
                array_push($strToReturn, $strRowArray[0]);
            return $strToReturn;
        }
        
        public function GetFieldsForTable($strTableName) {
            $objResult = $this->Query(sprintf('SELECT * FROM %s%s%s LIMIT 1', $this->strEscapeIdentifierBegin, $strTableName, $this->strEscapeIdentifierEnd));
            return $objResult->FetchFields();
        }

        public function InsertId($strTableName = null, $strColumnName = null) {
            return $this->objMySqli->insert_id;
        }

        public function Close() {
            $this->objMySqli->close();

            // Update Connected Flag
            $this->blnConnectedFlag = false;
        }

        protected function ExecuteTransactionBegin() {
            // Set to AutoCommit
            $this->NonQuery('SET AUTOCOMMIT=0;');
        }

        protected function ExecuteTransactionCommit() {
            $this->NonQuery('COMMIT;');
            // Set to AutoCommit
            $this->NonQuery('SET AUTOCOMMIT=1;');
        }

        protected function ExecuteTransactionRollBack() {
            $this->NonQuery('ROLLBACK;');
            // Set to AutoCommit
            $this->NonQuery('SET AUTOCOMMIT=1;');
        }

        public function GetFoundRows() {
            if (array_key_exists('usefoundrows', $this->objConfigArray) && $this->objConfigArray['usefoundrows']) {
                $objResult = $this->Query('SELECT FOUND_ROWS();');
                $strRow = $objResult->FetchArray();
                return $strRow[0];
            } else
                throw new QCallerException('Cannot call GetFoundRows() on the database when "usefoundrows" configuration was not set to true.');
        }

        public function GetIndexesForTable($strTableName) {
            // Figure out the Table Type (InnoDB, MyISAM, etc.) by parsing the Create Table description
            $strCreateStatement = $this->GetCreateStatementForTable($strTableName);
            $strTableType = $this->GetTableTypeForCreateStatement($strCreateStatement);

            switch (true) {
                case substr($strTableType, 0, 6) == 'MYISAM':
                    return $this->ParseForIndexes($strCreateStatement);

                case substr($strTableType, 0, 6) == 'INNODB':
                    return $this->ParseForIndexes($strCreateStatement);

                case substr($strTableType, 0, 6) == 'MEMORY':
                case substr($strTableType, 0, 4) == 'HEAP':
                    return $this->ParseForIndexes($strCreateStatement);

                default:
                    throw new Exception("Table Type is not supported: $strTableType");
            }
        }

        public function GetForeignKeysForTable($strTableName) {
            // Figure out the Table Type (InnoDB, MyISAM, etc.) by parsing the Create Table description
            $strCreateStatement = $this->GetCreateStatementForTable($strTableName);
            $strTableType = $this->GetTableTypeForCreateStatement($strCreateStatement);

            switch (true) {
                case substr($strTableType, 0, 6) == 'MYISAM':
                    $objForeignKeyArray = array();
                    break;

                case substr($strTableType, 0, 6) == 'MEMORY':
                case substr($strTableType, 0, 4) == 'HEAP':
                    $objForeignKeyArray = array();
                    break;

                case substr($strTableType, 0, 6) == 'INNODB':
                    $objForeignKeyArray = $this->ParseForInnoDbForeignKeys($strCreateStatement);
                    break;

                default:
                    throw new Exception("Table Type is not supported: $strTableType");
            }

            return $objForeignKeyArray;
        }

        // MySql defines KeyDefinition to be [OPTIONAL_NAME] ([COL], ...)
        // If the key name exists, this will parse it out and return it
        private function ParseNameFromKeyDefinition($strKeyDefinition) {
            $strKeyDefinition = trim($strKeyDefinition);

            $intPosition = strpos($strKeyDefinition, '(');

            if ($intPosition === false)
                throw new Exception("Invalid Key Definition: $strKeyDefinition");
            else if ($intPosition == 0)
                // No Key Name Defined
                return null;
            
            // If we're here, then we have a key name defined
            $strName = trim(substr($strKeyDefinition, 0, $intPosition));
            
            // Rip Out leading and trailing "`" character (if applicable)
            if (substr($strName, 0, 1) == '`')
                return substr($strName, 1, strlen($strName) - 2);
            else
                return $strName;
        }

        // MySql defines KeyDefinition to be [OPTIONAL_NAME] ([COL], ...)
        // This will return an array of strings that are the names [COL], etc.
        private function ParseColumnNameArrayFromKeyDefinition($strKeyDefinition) {
            $strKeyDefinition = trim($strKeyDefinition);
            
            // Get rid of the opening "(" and the closing ")"
            $intPosition = strpos($strKeyDefinition, '(');
            if ($intPosition === false)
                throw new Exception("Invalid Key Definition: $strKeyDefinition");
            $strKeyDefinition = trim(substr($strKeyDefinition, $intPosition + 1));

            $intPosition = strpos($strKeyDefinition, ')');
            if ($intPosition === false)
                throw new Exception("Invalid Key Definition: $strKeyDefinition");
            $strKeyDefinition = trim(substr($strKeyDefinition, 0, $intPosition));

            // Create the Array
            // TODO: Current method doesn't support key names with commas or parenthesis in them!
            $strToReturn = explode(',', $strKeyDefinition);

            // Take out trailing and leading "`" character in each name (if applicable)
            for ($intIndex = 0; $intIndex < count($strToReturn); $intIndex++) {
                $strColumn = $strToReturn[$intIndex];

                if (substr($strColumn, 0, 1) == '`')
                    $strColumn = substr($strColumn, 1, strpos($strColumn, '`', 1) - 1);

                $strToReturn[$intIndex] = $strColumn;
            }

            return $strToReturn;
        }

        private function ParseForIndexes($strCreateStatement) {
            // MySql nicely splits each object in a table into it's own line
            // Split the create statement into lines, and then pull out anything
            // that says "PRIMARY KEY", "UNIQUE KEY", or just plain ol' "KEY"
            $strLineArray = explode("\n", $strCreateStatement);

            $objIndexArray = array();
            
            // We don't care about the first line or the last line
            for ($intIndex = 1; $intIndex < (count($strLineArray) - 1); $intIndex++) {
                $strLine = $strLineArray[$intIndex];

                // Each object has a two-space indent
                // So this is a key object if any of those key-related words exist at position 2
                switch (2) {
                    case (strpos($strLine, 'PRIMARY KEY')):
                        $strKeyDefinition = substr($strLine, strlen('  PRIMARY KEY '));

                        $strKeyName = $this->ParseNameFromKeyDefinition($strKeyDefinition);
                        $strColumnNameArray = $this->ParseColumnNameArrayFromKeyDefinition($strKeyDefinition);

                        $objIndex = new QDatabaseIndex($strKeyName, $blnPrimaryKey = true, $blnUnique = true, $strColumnNameArray);
                        array_push($objIndexArray, $objIndex);
                        break;

                    case (strpos($strLine, 'UNIQUE KEY')):
                        $strKeyDefinition = substr($strLine, strlen('  UNIQUE KEY '));

                        $strKeyName = $this->ParseNameFromKeyDefinition($strKeyDefinition);
                        $strColumnNameArray = $this->ParseColumnNameArrayFromKeyDefinition($strKeyDefinition);

                        $objIndex = new QDatabaseIndex($strKeyName, $blnPrimaryKey = false, $blnUnique = true, $strColumnNameArray);
                        array_push($objIndexArray, $objIndex);
                        break;

                    case (strpos($strLine, 'KEY')):
                        $strKeyDefinition = substr($strLine, strlen('  KEY '));

                        $strKeyName = $this->ParseNameFromKeyDefinition($strKeyDefinition);
                        $strColumnNameArray = $this->ParseColumnNameArrayFromKeyDefinition($strKeyDefinition);

                        $objIndex = new QDatabaseIndex($strKeyName, $blnPrimaryKey = false, $blnUnique = false, $strColumnNameArray);
                        array_push($objIndexArray, $objIndex);
                        break;
                }
            }

            return $objIndexArray;
        }

        private function ParseForInnoDbForeignKeys($strCreateStatement) {
            // MySql nicely splits each object in a table into it's own line
            // Split the create statement into lines, and then pull out anything
            // that starts with "CONSTRAINT" and contains "FOREIGN KEY"
            $strLineArray = explode("\n", $strCreateStatement);

            $objForeignKeyArray = array();

            // We don't care about the first line or the last line
            for ($intIndex = 1; $intIndex < (count($strLineArray) - 1); $intIndex++) {
                $strLine = $strLineArray[$intIndex];

                // Check to see if the line:
                // * Starts with "CONSTRAINT" at position 2 AND
                // * contains "FOREIGN KEY"
                if ((strpos($strLine, "CONSTRAINT") == 2) &&
                    (strpos($strLine, "FOREIGN KEY") !== false)) {
                    $strLine = substr($strLine, strlen('  CONSTRAINT '));
                    
                    // By the end of the following lines, we will end up with a strTokenArray
                    // Index 0: the FK name
                    // Index 1: the list of columns that are the foreign key
                    // Index 2: the table which this FK references
                    // Index 3: the list of columns which this FK references
                    $strTokenArray = explode(' FOREIGN KEY ', $strLine);
                    $strTokenArray[1] = explode(' REFERENCES ', $strTokenArray[1]);
                    $strTokenArray[2] = $strTokenArray[1][1];
                    $strTokenArray[1] = $strTokenArray[1][0];
                    $strTokenArray[2] = explode(' ', $strTokenArray[2]);
                    $strTokenArray[3] = $strTokenArray[2][1];
                    $strTokenArray[2] = $strTokenArray[2][0];
                    
                    // Cleanup, and change Index 1 and Index 3 to be an array based on the
                    // parsed column name list
                    if (substr($strTokenArray[0], 0, 1) == '`')
                        $strTokenArray[0] = substr($strTokenArray[0], 1, strlen($strTokenArray[0]) - 2);
                    $strTokenArray[1] = $this->ParseColumnNameArrayFromKeyDefinition($strTokenArray[1]);
                    if (substr($strTokenArray[2], 0, 1) == '`')
                        $strTokenArray[2] = substr($strTokenArray[2], 1, strlen($strTokenArray[2]) - 2);
                    $strTokenArray[3] = $this->ParseColumnNameArrayFromKeyDefinition($strTokenArray[3]);
                    
                    // Create the FK object and add it to the return array
                    $objForeignKey = new QDatabaseForeignKey($strTokenArray[0], $strTokenArray[1], $strTokenArray[2], $strTokenArray[3]);
                    array_push($objForeignKeyArray, $objForeignKey);
                    
                    // Ensure the FK object has matching column numbers (or else, throw)
                    if ((count($objForeignKey->ColumnNameArray) == 0) ||
                        (count($objForeignKey->ColumnNameArray) != count($objForeignKey->ReferenceColumnNameArray)))
                        throw new Exception("Invalid Foreign Key definition: $strLine");
                }
            }
            return $objForeignKeyArray;
        }

        private function GetCreateStatementForTable($strTableName) {
            // Use the MySQL "SHOW CREATE TABLE" functionality to get the table's Create statement
            $objResult = $this->Query(sprintf('SHOW CREATE TABLE `%s`', $strTableName));
            $objRow = $objResult->FetchRow();
            $strCreateTable = $objRow[1];
            $strCreateTable = str_replace("\r", "", $strCreateTable);
            return $strCreateTable;
        }

        private function GetTableTypeForCreateStatement($strCreateStatement) {
            // Table Type is in the last line of the Create Statement, "TYPE=DbTableType"
            $strLineArray = explode("\n", $strCreateStatement);
            $strFinalLine = strtoupper($strLineArray[count($strLineArray) - 1]);

            if (substr($strFinalLine, 0, 7) == ') TYPE=') {
                return trim(substr($strFinalLine, 7));
            } else if (substr($strFinalLine, 0, 9) == ') ENGINE=') {
                return trim(substr($strFinalLine, 9));
            } else
                throw new Exception("Invalid Table Description");
        }

        /**
         * 
         * @param string $sql
         * @return QMySqliDatabaseResult
         */
        public function ExplainStatement($sql) {
            // As of MySQL 5.6.3, EXPLAIN provides information about
            // SELECT, DELETE, INSERT, REPLACE, and UPDATE statements.
            // Before MySQL 5.6.3, EXPLAIN provides information only about SELECT statements.
            
            $objDbResult = $this->Query("select version()");
            $strDbRow = $objDbResult->FetchRow();
            $strVersion = QType::Cast($strDbRow[0], QType::String);
            $strVersionArray = explode('.', $strVersion);
            $strMajorVersion = null;
            if (count($strVersionArray) > 0) {
                $strMajorVersion = $strVersionArray[0];
            }
            if (null === $strMajorVersion) {
                return null;
            }
            if (intval($strMajorVersion) > 5) {
                return $this->Query("EXPLAIN " . $sql);
            } else if (5 == intval($strMajorVersion)) {
                $strMinorVersion = null;
                if (count($strVersionArray) > 1) {
                    $strMinorVersion = $strVersionArray[1];
                }
                if (null === $strMinorVersion) {
                    return null;
                }
                if (intval($strMinorVersion) > 6) {
                    return $this->Query("EXPLAIN " . $sql);
                } else if (6 == intval($strMinorVersion)) {
                    $strSubMinorVersion = null;
                    if (count($strVersionArray) > 2) {
                        $strSubMinorVersion = $strVersionArray[2];
                    }
                    if (null === $strSubMinorVersion) {
                        return null;
                    }
                    if (!is_integer($strSubMinorVersion)) {
                        $strSubMinorVersionArray = explode("-", $strSubMinorVersion);
                        if (count($strSubMinorVersionArray) > 1) {
                           $strSubMinorVersion = $strSubMinorVersionArray[0];
                           if (!is_integer($strSubMinorVersion)) {
                               // Failed to determine the sub-minor version.
                               return null;
                           }
                        } else {
                            // Failed to determine the sub-minor version.
                            return null;
                        }
                    }
                    if (intval($strSubMinorVersion) > 2) {
                        return $this->Query("EXPLAIN " . $sql);
                    } else {
                        // We have the version before 5.6.3
                        // let's check if it is SELECT-only request
                        if (0 == substr_count($sql, "DELETE") &&
                            0 == substr_count($sql, "INSERT") &&
                            0 == substr_count($sql, "REPLACE") &&
                            0 == substr_count($sql, "UPDATE")
                        ) {
                            return $this->Query("EXPLAIN " . $sql);
                        }
                    }
                }
            }
            // Return null by default
            return null;
        }
    }

    /**
     *
     * @package DatabaseAdapters
     */
    class QMySqliDatabaseException extends QDatabaseExceptionBase {
        public function __construct($strMessage, $intNumber, $strQuery) {
            parent::__construct(sprintf("MySqli Error: %s", $strMessage), 2);
            $this->intErrorNumber = $intNumber;
            $this->strQuery = $strQuery;
        }
    }

    /**
     * Class to handle results sent by database upon querying
     * @package DatabaseAdapters
     */
    class QMySqliDatabaseResult extends QDatabaseResultBase {
        protected $objMySqliResult;
        protected $objDb;

        public function __construct(mysqli_result $objResult, QMySqliDatabase $objDb) {
            $this->objMySqliResult = $objResult;
            $this->objDb = $objDb;
        }

        public function FetchArray() {
            return $this->objMySqliResult->fetch_array();
        }

        public function FetchFields() {
            $objArrayToReturn = array();
            while ($objField = $this->objMySqliResult->fetch_field())
                array_push($objArrayToReturn, new QMySqliDatabaseField($objField, $this->objDb));
            return $objArrayToReturn;
        }

        public function FetchField() {
            if ($objField = $this->objMySqliResult->fetch_field())
                return new QMySqliDatabaseField($objField, $this->objDb);
        }

        public function FetchRow() {
            return $this->objMySqliResult->fetch_row();
        }

        public function MySqlFetchField() {
            return $this->objMySqliResult->fetch_field();
        }

        public function CountRows() {
            return $this->objMySqliResult->num_rows;
        }

        public function CountFields() {
            return $this->objMySqliResult->num_fields();
        }

        public function Close() {
            $this->objMySqliResult->free();
        }
        
        public function GetNextRow() {
            $strColumnArray = $this->FetchArray();
            
            if ($strColumnArray)
                return new QMySqliDatabaseRow($strColumnArray);
            else
                return null;
        }

        public function GetRows() {
            $objDbRowArray = array();
            while ($objDbRow = $this->GetNextRow())
                array_push($objDbRowArray, $objDbRow);
            return $objDbRowArray;
        }
    }

    /**
     *
     * @package DatabaseAdapters
     */
    class QMySqliDatabaseRow extends QDatabaseRowBase {
        protected $strColumnArray;

        public function __construct($strColumnArray) {
            $this->strColumnArray = $strColumnArray;
        }

        /**
         * Gets the value of a column from a result row returned by the database
         *
         * @param string          $strColumnName Name of the column
         * @param null|string     $strColumnType A QDatabaseFieldType string
         *
         * @return mixed
         */
        public function GetColumn($strColumnName, $strColumnType = null) {
            if (!isset($this->strColumnArray[$strColumnName])) {
                return null;
            }
            $strColumnValue = $this->strColumnArray[$strColumnName];

            switch ($strColumnType) {
                case QDatabaseFieldType::Bit:
                    // Account for single bit value
                    $chrBit = $strColumnValue;
                    if ((strlen($chrBit) == 1) && (ord($chrBit) == 0))
                        return false;

                    // Otherwise, use PHP conditional to determine true or false
                    return ($strColumnValue) ? true : false;

                case QDatabaseFieldType::Blob:
                case QDatabaseFieldType::Char:
                case QDatabaseFieldType::VarChar:
                    return QType::Cast($strColumnValue, QType::String);

                case QDatabaseFieldType::Date:
                    return new QDateTime($strColumnValue, null, QDateTime::DateOnlyType);
                case QDatabaseFieldType::DateTime:
                    return new QDateTime($strColumnValue, null, QDateTime::DateAndTimeType);
                case QDatabaseFieldType::Time:
                    return new QDateTime($strColumnValue, null, QDateTime::TimeOnlyType);

                case QDatabaseFieldType::Float:
                    return QType::Cast($strColumnValue, QType::Float);

                case QDatabaseFieldType::Integer:
                    return QType::Cast($strColumnValue, QType::Integer);

                default:
                    return $strColumnValue;
            }
        }

        /**
         * Tells whether a particular column exists in a returned database row
         *
         * @param string $strColumnName Name of te column
         *
         * @return bool
         */
        public function ColumnExists($strColumnName) {
            return array_key_exists($strColumnName, $this->strColumnArray);
        }

        public function GetColumnNameArray() {
            return $this->strColumnArray;
        }
    }

    /**
     *
     * @package DatabaseAdapters
     */
    class QMySqliDatabaseField extends QDatabaseFieldBase {
        public function __construct($mixFieldData, $objDb = null) {
            $this->strName = $mixFieldData->name;
            $this->strOriginalName = $mixFieldData->orgname;
            $this->strTable = $mixFieldData->table;
            $this->strOriginalTable = $mixFieldData->orgtable;
            $this->strDefault = $mixFieldData->def;
            $this->intMaxLength = null;
            $this->strComment = null;

            // Set strOriginalName to Name if it isn't set
            if (!$this->strOriginalName)
                $this->strOriginalName = $this->strName;
            
            if($this->strOriginalTable)
            {
                $objDescriptionResult = $objDb->Query(sprintf("SHOW FULL FIELDS FROM `%s`", $this->strOriginalTable));
                while (($objRow = $objDescriptionResult->FetchArray())) {
                    if ($objRow["Field"] == $this->strOriginalName) {
                         
                         $this->strDefault = $objRow["Default"]; 
                        // Calculate MaxLength of this column (e.g. if it's a varchar, calculate length of varchar
                        // NOTE: $mixFieldData->max_length in the MySQL spec is **DIFFERENT**
                        $strLengthArray = explode("(", $objRow["Type"]);
                        if ((count($strLengthArray) > 1) &&
                            (strtolower($strLengthArray[0]) != 'enum') &&
                            (strtolower($strLengthArray[0]) != 'set')) {
                            $strLengthArray = explode(")", $strLengthArray[1]);
                            $this->intMaxLength = $strLengthArray[0];

                            // If the length is something like (7,2), then let's pull out just the "7"
                            $intCommaPosition = strpos($this->intMaxLength, ',');
                            if ($intCommaPosition !== false) {
                                $this->intMaxLength = substr($this->intMaxLength, 0, $intCommaPosition);
                                $this->intMaxLength++; // this is a decimal, so max length should include the decimal point too.
                            }

                            if (!is_numeric($this->intMaxLength)) {
                                throw new Exception("Not a valid Column Length: " . $objRow["Type"]);
                            }
                        }
                        
                        // Get the field comment
                        $this->strComment = $objRow["Comment"];
                    }
                }
            }

            $this->blnIdentity = ($mixFieldData->flags & MYSQLI_AUTO_INCREMENT_FLAG) ? true: false;
            $this->blnNotNull = ($mixFieldData->flags & MYSQLI_NOT_NULL_FLAG) ? true : false;
            $this->blnPrimaryKey = ($mixFieldData->flags & MYSQLI_PRI_KEY_FLAG) ? true : false;
            $this->blnUnique = ($mixFieldData->flags & MYSQLI_UNIQUE_KEY_FLAG) ? true : false;
            
            $this->SetFieldType($mixFieldData->type);

            // Special situation that we take advantage of to automatically implement optimistic locking
            if ($mixFieldData->type == MYSQLI_TYPE_TIMESTAMP &&
                    $mixFieldData->flags & MYSQLI_ON_UPDATE_NOW_FLAG) {
                $this->strType = QDatabaseFieldType::VarChar;
                $this->blnTimestamp = true;
            }
        }

        protected function SetFieldType($intMySqlFieldType) {

            if (version_compare(PHP_VERSION, '5.6.15') >= 0) {
                if (defined("MYSQLI_TYPE_JSON") && $intMySqlFieldType == MYSQLI_TYPE_JSON) {
                    $this->strType = QDatabaseFieldType::Json;
                    return;
                }
            }
            switch ($intMySqlFieldType) {
                case MYSQLI_TYPE_TINY:
                    if ($this->intMaxLength == 1)
                        $this->strType = QDatabaseFieldType::Bit;
                    else
                        $this->strType = QDatabaseFieldType::Integer;
                    break;
                case MYSQLI_TYPE_SHORT:
                case MYSQLI_TYPE_LONG:
                case MYSQLI_TYPE_LONGLONG:
                case MYSQLI_TYPE_INT24:
                    $this->strType = QDatabaseFieldType::Integer;
                    break;
                case MYSQLI_TYPE_NEWDECIMAL:
                case MYSQLI_TYPE_DECIMAL:
                    // NOTE: PHP's best response to fixed point exact precision numbers is to use the bcmath library.
                    // bcmath requires string inputs. If you try to do math directly on these, PHP will convert to float,
                    // so for those who care, they will need to be careful. For those who do not care, then PHP will do
                    // the conversion anyway.
                    $this->strType = QDatabaseFieldType::VarChar;
                    break;

                case MYSQLI_TYPE_FLOAT:
                    $this->strType = QDatabaseFieldType::Float;
                    break;
                case MYSQLI_TYPE_DOUBLE:
                    // NOTE: PHP does not offer full support of double-precision floats.
                    // Value will be set as a VarChar which will guarantee that the precision will be maintained.
                    //    However, you will not be able to support full typing control (e.g. you would
                    //    not be able to use a QFloatTextBox -- only a regular QTextBox)
                    $this->strType = QDatabaseFieldType::VarChar;
                    break;
                case MYSQLI_TYPE_DATE:
                    $this->strType = QDatabaseFieldType::Date;
                    break;
                case MYSQLI_TYPE_TIME:
                    $this->strType = QDatabaseFieldType::Time;
                    break;
                case MYSQLI_TYPE_TIMESTAMP:
                case MYSQLI_TYPE_DATETIME:
                    $this->strType = QDatabaseFieldType::DateTime;
                    break;
                case MYSQLI_TYPE_TINY_BLOB:
                case MYSQLI_TYPE_MEDIUM_BLOB:
                case MYSQLI_TYPE_LONG_BLOB:
                case MYSQLI_TYPE_BLOB:
                    $this->strType = QDatabaseFieldType::Blob;
                    break;
                case MYSQLI_TYPE_STRING:
                case MYSQLI_TYPE_VAR_STRING:
                    $this->strType = QDatabaseFieldType::VarChar;
                    break;
                case MYSQLI_TYPE_CHAR:
                    $this->strType = QDatabaseFieldType::Char;
                    break;
                case MYSQLI_TYPE_INTERVAL:
                    throw new Exception("QCubed MySqliDatabase library: MYSQLI_TYPE_INTERVAL is not supported");
                    break;
                case MYSQLI_TYPE_NULL:
                    throw new Exception("QCubed MySqliDatabase library: MYSQLI_TYPE_NULL is not supported");
                    break;
                case MYSQLI_TYPE_YEAR:
                    $this->strType = QDatabaseFieldType::Integer;
                    break;
                case MYSQLI_TYPE_NEWDATE:
                    throw new Exception("QCubed MySqliDatabase library: MYSQLI_TYPE_NEWDATE is not supported");
                    break;
                case MYSQLI_TYPE_ENUM:
                    throw new Exception("QCubed MySqliDatabase library: MYSQLI_TYPE_ENUM is not supported.  Use TypeTables instead.");
                    break;
                case MYSQLI_TYPE_SET:
                    throw new Exception("QCubed MySqliDatabase library: MYSQLI_TYPE_SET is not supported.  Use TypeTables instead.");
                    break;
                case MYSQLI_TYPE_GEOMETRY:
                    throw new Exception("QCubed MySqliDatabase library: MYSQLI_TYPE_GEOMETRY is not supported");
                    break;
                default:
                    throw new Exception("Unable to determine MySqli Database Field Type: " . $intMySqlFieldType);
                    break;
            }
        }
    }