<?php
	/**
	 * Database Adapter for Microsoft SQL Server 2005/2008
	 * Utilizes the native Microsoft SQL Server extension php_sqlsrv.dll version 1.1 (win)
	 * http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx
         *
         * Requirements:
         * - Windows XP SP3/2003 SP1/Vista/2008/7 or later
         * - Microsoft SQL Server 2008 Native Client
         * - PHP 5.2.4 or later
         * - SQL Server Driver for PHP 1.1
	 *
	 * @package DatabaseAdapters
	 */

	/**
	 * Database Adapter for Microsoft SQL Server 2005/2008
	 *
	 *
	 * LimitInfo and Query utilizes an interal SQL tag QCUBED_OFFSET<#>, where # represents
	 * the number of rows to offset for "Limit"-based queries.  The QCUBED_OFFSET is added
	 * internally by SqlLimitVariablePrefix(), and it is handled (and removed from the query)
	 * by Query().  In error messages and DB profiling, the QCUBED_OFFSET<#> tag *WILL* appear
	 * (if applicable).  The framework will handle this gracefully, but obviously, if you try
	 * and cut and paste SQL code that contains QCUBED_OFFSET<#> into QueryAnalyzer, the query
	 * will fail, so just be aware of that.  If you want to do something like test queries
	 * with QueryAnalyzer, just remember to manually remove any QCUBED_OFFSET<#> information.
	 *
	 * ENHANCEMENTS over MSSQL based-driver:
	 * - varchar limit of 254 characters no longer exists, uses full varchar(8000)
         * - supports different date formats
         * - faster
	 *
	 * DATE VALUES:
	 * If the date format of your database is different from the standard english format of
	 * 'YYYY-MM-DD hhhh:mm:ss' you have to declare it by setting the option 'dateformat'
	 * in the database connection array in the configuration.inc.php.
	 * For instance if your database wants german date settings you would set it to
	 * 'DD.MM.YYYY hhhh:mm:ss'.
	 *
	 * LIMITATIONS:
	 * - GROUP BY
	 * Using GROUP BY clause is broken because:
	 * a) Current QQuery implementation is MySQL specific. Standard SQL wants every column
	 * in the SELECT statement, that is not an aggregate statement, in the GROUP BY clause
	 * and this behaviour is currently not supported. This limitation is valid too for the
	 * MSSQL based adapter.
	 *
	 * - Datatypes currently not fully supported
	 * sql_variant, varbinary(max), geometry, geography, hierarchyid, uniqueidentifier
	 *
	 *
	 * @copyright	Copyright (C) 2009,2010 Andreas Krohn
	 * @author	Andreas Krohn <akrohn.pronet@googlemail.com>, based on code by Mike Ho
	 * @license	http://www.opensource.org/licenses/mit-license.php
	 * @package	DatabaseAdapters
	 */
	class QSqlServer2005Database extends QDatabaseBase {
		const Adapter = 'Microsoft SQL Server 2005/2008 Database Adapter';

		protected $objSqlSrvConn;
		protected $objMostRecentStatement; // is always the last executed statement

		protected $strEscapeIdentifierBegin = '[';
		protected $strEscapeIdentifierEnd = ']';
		protected $blnOnlyFullGroupBy = true;

		// Default query options array
		protected $mixedOptionsArray = array(
		);

		/**
		 * Returns extended error information about the last sqlsrv operation performed
		 *
		 * @param string strError Returns Errormessage By Reference
		 * @param string strLastErrorCode Returns last ErrorCode By Reference
		 * @return string Errorinformation
		 */
		private function GetErrorInformation(&$strError, &$strLastErrorCode) {
			$strError = '';
			$strLastErrorCode = '';

			// Determine only the errorinformation
			$objErrors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
			if(!is_null($objErrors)) {
				// Get all errors
				foreach($objErrors as $strErrorArray) {
					$strError .= ' SQLSTATE: '.$strErrorArray['SQLSTATE'].', Code: '.$strErrorArray['code'].', Message: '.$strErrorArray['message'];
					$strLastErrorCode = $strErrorArray['code'];
				}
			}
		}

		/**
		 * Properly escapes $mixData to be used as a SQL query parameter.
		 * If IncludeEquality is set (usually not), then include an equality operator.
		 * So for most data, it would just be "=".  But, for example,
		 * if $mixData is NULL, then most RDBMS's require the use of "IS".
		 *
		 * @param mixed $mixData
		 * @param boolean $blnIncludeEquality whether or not to include an equality operator
		 * @param boolean $blnReverseEquality whether the included equality operator should be a "NOT EQUAL", e.g. "!="
		 * @return string the properly formatted SQL variable
		 */
		public function SqlVariable($mixData, $blnIncludeEquality = false, $blnReverseEquality = false) {
			// Are we SqlVariabling a BOOLEAN value?
			if (is_bool($mixData)) {
				// Yes
				if ($blnIncludeEquality) {
					// We must include the inequality

					if ($blnReverseEquality) {
						// Do a "Reverse Equality"

						// Check against NULL, True then False
						if (is_null($mixData))
							return 'IS NOT NULL';
						else if ($mixData)
							return '= 0';
						else
							return '!= 0';
					} else {
						// Check against NULL, True then False
						if (is_null($mixData))
							return 'IS NULL';
						else if ($mixData)
							return '!= 0';
						else
							return '= 0';
					}
				} else {
					// Check against NULL, True then False
					if (is_null($mixData))
						return 'NULL';
					else if ($mixData)
						return '1';
					else
						return '0';
				}
			}

			// Check for Equality Inclusion
			if ($blnIncludeEquality) {
				if ($blnReverseEquality) {
					if (is_null($mixData))
						$strToReturn = 'IS NOT ';
					else
						$strToReturn = '!= ';
				} else {
					if (is_null($mixData))
						$strToReturn = 'IS ';
					else
						$strToReturn = '= ';
				}
			} else
				$strToReturn = '';

			// Check for NULL Value
			if (is_null($mixData))
				return $strToReturn . 'NULL';

			// Check for NUMERIC Value
			if (is_integer($mixData) || is_float($mixData))
				return $strToReturn . sprintf('%s', $mixData);

			// Check for DATE Value and use the desired db-date-format
			if ($mixData instanceof QDateTime) {
				return $strToReturn . sprintf("'%s'", $mixData->qFormat($this->DateFormat));
			}

			// Assume it's some kind of string value
			return $strToReturn . sprintf("'%s'", str_replace("'", "''", $mixData));
		}

		public function SqlLimitVariablePrefix($strLimitInfo) {
			// Setup limit suffix (if applicable) via a TOP clause
			// Add QCUBED_OFFSET tag if applicable

			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');

				// First figure out if we HAVE an offset
				$strArray = explode(',', $strLimitInfo);

				if (count($strArray) == 2) {
					// Yep -- there's an offset
					return sprintf(
						'TOP %s QCUBED_OFFSET<%s>',
						($strArray[0] + $strArray[1]),
						$strArray[0]);
				} else if (count($strArray) == 1) {
					return 'TOP ' . $strArray[0];
				} else {
					throw new QSqlServer2005DatabaseException('Invalid Limit Info: ' . $strLimitInfo, 0, null);
				}
			}

			return null;
		}

		public function SqlLimitVariableSuffix($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 Connect() {
			// Lookup Adapter-Specific Connection Properties
			$strServer = $this->Server;
			$strName = $this->Database;
			$strUsername = $this->Username;
			$strPassword = $this->Password;
			$strPort = $this->Port;

			if ($strPort) {
				// Windows Servers
				if (array_key_exists('OS', $_SERVER) && stristr($_SERVER['OS'], 'Win') !== false)
					$strServer .= ',' . $strPort;
				// All Other Servers
				else
					$strServer .= ':' . $strPort;
			}

			// define the characterset for the sqlsrv driver
			// special handling for utf-8 data
			$strCharacterset = (QApplication::$EncodingType == 'UTF-8') ? 'UTF-8' : 'SQLSRV_ENC_CHAR';

			// Connect to the Database Server

			// Disable warnings as errors behavior
			sqlsrv_configure("WarningsReturnAsErrors", 0);

			// Set connection parameters
			$strConnectionInfoArray = array(
				'UID'=>$strUsername
				, 'PWD'=>$strPassword
				, 'Database'=>$strName
				, 'CharacterSet'=>$strCharacterset
			);

			// Connect using SQL Server Authentication
			$this->objSqlSrvConn = sqlsrv_connect($strServer, $strConnectionInfoArray);
			if($this->objSqlSrvConn === false) {
			    // Determine the errorinformation
			    $this->GetErrorInformation($strErrorinformation, $strErrorCode);
			    $objException = new QSqlServer2005DatabaseException('Unable to connect: ' . $strErrorinformation, $strErrorCode, null);
			    $objException->IncrementOffset();
			    throw $objException;
			}

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

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

		protected function ExecuteQuery($strQuery) {
			// First, check for QCUBED_OFFSET<#> for LIMIT INFO Offseting
			if ( ($intPosition = strpos($strQuery, 'QCUBED_OFFSET<')) !== false) {
				$intEndPosition = strpos($strQuery, '>', $intPosition);
				if ($intEndPosition === false)
					throw new QSqlServer2005DatabaseException('Invalid QCUBED_OFFSET', 0, $strQuery);
				$intOffset = QType::Cast(substr($strQuery,
					$intPosition + 13 /* len of QCUBED_OFFSET< */,
					$intEndPosition - $intPosition - 13), QType::Integer);
				$strQuery = substr($strQuery, 0, $intPosition) . substr($strQuery, $intEndPosition + 1);
			} else
				$intOffset = 0;

			// Perform the Query
			$objResult = sqlsrv_query($this->objSqlSrvConn, $strQuery, NULL, $this->mixedOptionsArray);
			if ($objResult === false) {
				// Determine the errorinformation
				$this->GetErrorInformation($strErrorinformation, $strErrorCode);
				throw new QSqlServer2005DatabaseException($strErrorinformation, $strErrorCode, $strQuery);
			}

			// Remember most recent statement
			$this->objMostRecentStatement = $objResult;

			// Return the Result
			$objSqlServerDatabaseResult = new QSqlServer2005DatabaseResult($objResult, $this);

			// Perform Offsetting (if applicable)
			for ($intIndex = 0; $intIndex < $intOffset; $intIndex++) {
				$objRow = $objSqlServerDatabaseResult->FetchRow();
				if (!$objRow)
					return $objSqlServerDatabaseResult;
			}

			return $objSqlServerDatabaseResult;
		}

		protected function ExecuteNonQuery($strNonQuery) {
			// Perform the NonQuery
			$objResult = sqlsrv_query($this->objSqlSrvConn, $strNonQuery, NULL, $this->mixedOptionsArray);
			if ($objResult === false) {
				// Determine the errorinformation
				$this->GetErrorInformation($strErrorinformation, $strErrorCode);
				throw new QSqlServer2005DatabaseException($strErrorinformation, $strErrorCode, $strNonQuery);
			}

			// Remember most recent statement
			$this->objMostRecentStatement = $objResult;
		}

		public function GetTables() {
			$objResult = $this->Query("
				SELECT  obj.name
				FROM    sys.objects obj
				WHERE   obj.type = 'U' AND
					obj.name NOT LIKE N'#%' AND
					obj.is_ms_shipped = 0
				ORDER BY obj.name ASC
			");

			$strToReturn = array();
			while ($strRowArray = $objResult->FetchRow()) {
				array_push($strToReturn, $strRowArray[0]);
			}
			return $strToReturn;
		}

		public function GetTableForId($intTableId) {
			$intTableId = $this->SqlVariable($intTableId);
			$strQuery = sprintf('
				SELECT  obj.name
				FROM    sys.objects obj
				WHERE   obj.object_id = %s
			', $intTableId);

			$objResult = $this->Query($strQuery);
			$objRow = $objResult->FetchRow();
			return $objRow[0];
		}

		public function GetFieldsForTable($strTableName) {
			$strTableName = $this->SqlVariable($strTableName);

			$strQuery = sprintf("
				SELECT  col.*
				    , typ.name AS data_type
				    , typ.scale AS scale
				    , obj.name AS table_name
				    , CAST(ex.value AS VARCHAR(8000)) AS comment
				FROM sys.columns col
				JOIN sys.objects obj ON obj.object_id = col.object_id 
				JOIN sys.types typ ON typ.system_type_id = col.system_type_id AND typ.user_type_id = col.user_type_id
				LEFT JOIN sys.extended_properties ex ON ex.major_id = col.object_id AND ex.minor_id = col.column_id AND ex.name = 'MS_Description' AND ex.class = 1 
				WHERE   obj.name = %s
				ORDER BY col.column_id ASC
			", $strTableName);

			$objResult = $this->Query($strQuery);

			$objFields = array();

			while ($objRow = $objResult->GetNextRow()) {
				array_push($objFields, new QSqlServer2005DatabaseField($objRow, $this));
			}

			return $objFields;
		}

		public function InsertId($strTableName = null, $strColumnName = null) {
			$strQuery = 'SELECT SCOPE_IDENTITY();';
			$objResult = $this->Query($strQuery);
			$objRow = $objResult->FetchRow();
			return $objRow[0];
		}

		public function Close() {
			sqlsrv_close($this->objSqlSrvConn);

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

		/**
		 * Begin transaction
		 */
		protected function ExecuteTransactionBegin() {
			if (sqlsrv_begin_transaction($this->objSqlSrvConn) === false) {
				// Determine the errorinformation
				$this->GetErrorInformation($strErrorinformation, $strErrorCode);
				throw new QSqlServer2005DatabaseException($strErrorinformation, $strErrorCode, $strNonQuery);
			}
		}

		/**
		 * Commit transaction
		 */
		protected function ExecuteTransactionCommit() {
			if (sqlsrv_commit($this->objSqlSrvConn) === false) {
				// Determine the errorinformation
				$this->GetErrorInformation($strErrorinformation, $strErrorCode);
				throw new QSqlServer2005DatabaseException($strErrorinformation, $strErrorCode, $strNonQuery);
			}
		}

		/**
		 * Rollback transaction
		 */
		protected function ExecuteTransactionRollBack() {
			if (sqlsrv_rollback($this->objSqlSrvConn) === false) {
				// Determine the errorinformation
				$this->GetErrorInformation($strErrorinformation, $strErrorCode);
				throw new QSqlServer2005DatabaseException($strErrorinformation, $strErrorCode, $strNonQuery);
			}
		}

		public function GetIndexesForTable($strTableName) {
			$objIndexArray = array();

			// Use sp_helpindex to pull the indexes
			$objResult = $this->Query(sprintf("EXEC sp_helpindex %s", $this->SqlVariable($strTableName)));
			while ($objRow = $objResult->GetNextRow()) {
				$strIndexDescription = $objRow->GetColumn('index_description');
				$strKeyName = $objRow->GetColumn('index_name');
				$blnPrimaryKey = (strpos($strIndexDescription, 'primary key') !== false);
				$blnUnique = (strpos($strIndexDescription, 'unique') !== false);
				$strColumnNameArray = explode(', ', $objRow->GetColumn('index_keys'));

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

			return $objIndexArray;
		}

		public function GetForeignKeysForTable($strTableName) {
			$objForeignKeyArray = array();

			// Use Query to pull the FKs
			$strQuery = sprintf("
				SELECT  fk_table = FK.TABLE_NAME
				  , fk_column = CU.COLUMN_NAME
				  , pk_table = PK.TABLE_NAME
				  , pk_column = PT.COLUMN_NAME
				  , constraint_name = C.CONSTRAINT_NAME
				FROM    INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C
				INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME
				INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME
				INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
				INNER JOIN (SELECT  i1.TABLE_NAME
					      , i2.COLUMN_NAME
					FROM    INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1
					INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME
					WHERE   i1.CONSTRAINT_TYPE = 'PRIMARY KEY') PT ON PT.TABLE_NAME = PK.TABLE_NAME
				WHERE   FK.TABLE_NAME = %s
				ORDER BY C.constraint_name
			",
			$this->SqlVariable($strTableName));
			$objResult = $this->Query($strQuery);

			$strKeyName = '';
			while ($objRow = $objResult->GetNextRow()) {
				if ($strKeyName != $objRow->GetColumn('constraint_name')) {
					if ($strKeyName) {
						$objForeignKey = new QDatabaseForeignKey(
							$strKeyName,
							$strColumnNameArray,
							$strReferenceTableName,
							$strReferenceColumnNameArray);
						array_push($objForeignKeyArray, $objForeignKey);
					}

					$strKeyName = $objRow->GetColumn('constraint_name');
					$strReferenceTableName = $objRow->GetColumn('pk_table');
					$strColumnNameArray = array();
					$strReferenceColumnNameArray = array();
				}

				if (!array_search($objRow->GetColumn('fk_column'), $strColumnNameArray)) {
					array_push($strColumnNameArray, $objRow->GetColumn('fk_column'));
				}

				if (!array_search($objRow->GetColumn('pk_column'), $strReferenceColumnNameArray)) {
					array_push($strReferenceColumnNameArray, $objRow->GetColumn('pk_column'));
				}
			}

			if ($strKeyName) {
				$objForeignKey = new QDatabaseForeignKey(
					$strKeyName,
					$strColumnNameArray,
					$strReferenceTableName,
					$strReferenceColumnNameArray);
				array_push($objForeignKeyArray, $objForeignKey);
			}

			// Return the Array of Foreign Keys
			return $objForeignKeyArray;
		}
	}

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

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

		public function __construct($objResult, QSqlServer2005Database $objDb) {
			$this->objSqlSrvResult = $objResult;
			$this->objDb = $objDb;
		}

		public function FetchArray() {
			$strColumnArray = sqlsrv_fetch_array($this->objSqlSrvResult);
			if ($strColumnArray === false) {
				// Determine the errorinformation
				$strErrorInformation 	= '';
				$strErrorCode		= '';
				$this->GetErrorInformation($strErrorinformation, $strErrorCode);
				throw new QSqlServer2005DatabaseException($strErrorinformation, $strErrorCode, $strNonQuery);
			}
			return $strColumnArray;
		}

		/**
		 * Function is never used
		 * @todo
		 */
		public function FetchFields() {
			return null;  // Not used
		}

		/**
		 * Function is never used
		 * @todo
		 */
		public function FetchField() {
			return null;  // Not used
		}

		public function FetchRow() {
			return sqlsrv_fetch_array($this->objSqlSrvResult, SQLSRV_FETCH_NUMERIC);
		}

		public function CountRows() {
			return sqlsrv_num_rows($this->objSqlSrvResult);
		}

		public function CountFields() {
			return sqlsrv_num_fields($this->objSqlSrvResult);
		}

		public function Close() {
			sqlsrv_free_stmt($this->objSqlSrvResult);
		}

		public function GetNextRow() {
			$strColumnArray = $this->FetchArray();

			if (!is_null($strColumnArray))
				return new QSqlServer2005DatabaseRow($strColumnArray);
			else
				return null;
		}

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

	/**
	 *
	 * @package DatabaseAdapters
	 */	
	class QSqlServer2005DatabaseRow 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 te column
		 * @param null|QDatabaseFieldType $strColumnType Data type
		 *
		 * @return mixed
		 */
		public function GetColumn($strColumnName, $strColumnType = null) {
			if (!isset($this->strColumnArray[$strColumnName])) {
				return null;
			}
			$strColumnValue = $this->strColumnArray[$strColumnName];
	
			switch ($strColumnType) {
				case QDatabaseFieldType::Bit:
					return ($strColumnValue) ? true : false;

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

				case QDatabaseFieldType::Date:
				case QDatabaseFieldType::DateTime:
				case QDatabaseFieldType::Time:
					return new QDateTime($strColumnValue);

				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 QSqlServer2005DatabaseField extends QDatabaseFieldBase {
		public function __construct($mixFieldData, $objDb = null) {
			$objDatabaseRow = null;
			try {
			    $objDatabaseRow = QType::Cast($mixFieldData, 'QSqlServer2005DatabaseRow');
			} catch (QInvalidCastException $objExc) {
			}

			if ($objDatabaseRow) {
				// Passed in field data is a row from select * from sys.columns for this table
				$intTableId = $objDatabaseRow->GetColumn('object_id');
				$this->strName = $objDatabaseRow->GetColumn('name');
				$this->strOriginalName = $this->strName;
				$this->strTable = $objDatabaseRow->GetColumn('table_name');
				$this->strOriginalTable = $this->strTable;
				$this->strDefault = null; /* Not Supported */
				$this->intMaxLength = $objDatabaseRow->GetColumn('max_length', QDatabaseFieldType::Integer);
				$this->blnNotNull = ($objDatabaseRow->GetColumn('is_nullable')) ? false : true;
				$this->blnIdentity = ($objDatabaseRow->GetColumn('is_identity')) ? true : false;
				$this->strType = $objDatabaseRow->GetColumn('data_type');
				$this->strComment = $objDatabaseRow->GetColumn('comment'); 

				$intScale = $objDatabaseRow->GetColumn('scale', QDatabaseFieldType::Integer);

				// PRIMARY KEY / UNIQUE
				$this->blnPrimaryKey = false;
				$this->blnUnique = false;
				// Determine it with the standardized information_schema views
				$objResult = $objDb->Query(sprintf("
					SELECT   kcu.column_name
					      , tc.constraint_type
					FROM     information_schema.table_constraints tc
					      , information_schema.key_column_usage kcu
					WHERE    tc.table_name = %s
						AND tc.constraint_type IN ('UNIQUE', 'PRIMARY KEY')
						AND kcu.table_name = tc.table_name
						AND kcu.table_schema = tc.table_schema
						AND kcu.constraint_name = tc.constraint_name
				", $objDb->SqlVariable($this->strTable)));
				// Search for current column and look if it's a primary key or unique
				while ($objRow = $objResult->GetNextRow()) {
					if ($objRow->GetColumn('column_name') == $this->strName) {
						// Primary Key?
						if ($objRow->GetColumn('constraint_type') == 'PRIMARY KEY') {
							$this->blnPrimaryKey = true;
							$this->blnUnique = true; // is also UNIQUE
						}
						// Unique?
						if ($objRow->GetColumn('constraint_type') == 'UNIQUE') {
							$this->blnUnique = true;
						}
					}
				}

				switch ($this->strType) {
					case 'numeric':
					case 'decimal':
						if ($intScale == 0)
							$this->strType = QDatabaseFieldType::Integer;
						else
							$this->strType = QDatabaseFieldType::Float;
						break;
					case 'bigint':
					case 'int':
					case 'tinyint':
					case 'smallint':
						$this->strType = QDatabaseFieldType::Integer;
						break;
					case 'money':
					case 'real':
					case 'float':
					case 'smallmoney':
						$this->strType = QDatabaseFieldType::Float;
						break;
					case 'bit':
						$this->strType = QDatabaseFieldType::Bit;
						break;
					case 'char':
					case 'nchar':
						$this->strType = QDatabaseFieldType::Char;
						break;
					case 'varchar':
					case 'nvarchar':
					case 'sysname':
					case 'uniqueidentifier':
						// varchar(max) and nvarchar(max) are recognized by intMaxLength = -1 and will be a blob-type
						if ($this->intMaxLength > -1) {
							$this->strType = QDatabaseFieldType::VarChar;
						}
						else {
							$this->strType = QDatabaseFieldType::Blob;
							$this->intMaxLength = null;
						}
						break;
					case 'text':
					case 'ntext':
					case 'binary':
					case 'image':
					case 'varbinary':
					case 'xml':
					case 'udt':
					case 'geometry':
					case 'geography':
					case 'hierarchyid':
					case 'sql_variant':
						$this->strType = QDatabaseFieldType::Blob;
						$this->intMaxLength = null;
						break;
					case 'datetime':
					case 'datetime2':
					case 'smalldatetime':
						$this->strType = QDatabaseFieldType::DateTime;
						break;
					case 'date':
						$this->strType = QDatabaseFieldType::Date;
						break;
					case 'time':
						$this->strType = QDatabaseFieldType::Time;
						break;
					case 'timestamp':
						// System-generated Timestamp values need to be treated as plain text
						$this->strType = QDatabaseFieldType::VarChar;
						$this->blnTimestamp = true;
						break;
					default:
						throw new QSqlServer2005DatabaseException('Unsupported DataType: ' . $this->strType, 0, null);
				}
			} else {
				// Passed in fielddata is a sqlsrv_fetch_field field result
				$this->strName = $mixFieldData->name;
				$this->strOriginalName = $mixFieldData->name;
				$this->strTable = $mixFieldData->column_source;
				$this->strOriginalTable = $mixFieldData->column_source;
				$this->intMaxLength = $mixFieldData->max_length;
			}
		}
	}