php-sqlsrv/test/sqlsrv/tools.inc

872 lines
22 KiB
PHP

<?php
// Numeric values that corresponds to a SQL type:
define("_SQL_BIGINT", -5);
define("_SQL_BINARY", -2);
define("_SQL_BIT", -7);
define("_SQL_CHAR", 1);
define("_SQL_TYPE_DATE", 91);
define("_SQL_TYPE_TIMESTAMP", 93);
define("_SQL_DECIMAL", 3);
define("_SQL_FLOAT", 6);
define("_SQL_LONGVARBINARY", -4);
define("_SQL_INTEGER", 4);
define("_SQL_WCHAR", -8);
define("_SQL_WLONGVARCHAR", -10);
define("_SQL_NUMERIC", 2);
define("_SQL_WVARCHAR", -9);
define("_SQL_REAL", 7);
define("_SQL_SMALLINT", 5);
define("_SQL_LONGVARCHAR", -1);
define("_SQL_TINYINT", -6);
define("_SQL_VARBINARY", -3);
define("_SQL_VARCHAR", 12);
define("_SQL_SS_XML", -152);
define("_SQL_GUID", -11);
define("_SQL_SS_TIME2", -154);
define("_SQL_SS_TIMESTAMPOFFSET", -155);
define("_CHUNK_SIZE", 8192);
define("_EPSILON", 0.00001);
function Verify($stmt, $metadata, $numFields, $encoding)
{
$i = 0;
while ($result = sqlsrv_fetch($stmt))
{
echo "Comparing data in row " . ++$i . "\n";
$data = GetInputData($i);
$dataArray = SimplifyDataArray($data, $metadata, $i);
for ($j = 0; $j < $numFields; $j++)
{
$value = sqlsrv_get_field($stmt, $j, SQLSRV_PHPTYPE_STRING($encoding));
CompareData($metadata, $i, $j, $value, $dataArray[$j], False, False);
}
}
// returns the number of rows fetched
return $i;
}
function ConvertDataToString($type, $data)
{
if ($data === null)
return "";
// Convert raw data to strings for datetime and binary types only
// Do nothing for other types
//
if (IsDateTimeType($type))
{
if ($type != _SQL_SS_TIME2)
return date_format( $data, 'Y-m-d H:i:s' );
else
return date_format( $data, 'H:i:s' );
}
else if (IsBinaryType($type))
{
return bin2hex($data);
}
else
{
return $data;
}
}
function CompareCharacterData($actual, $expected, $errorMode = false)
{
// $errorMode is true if an error is expected
$matched = false;
if ($actual === $expected)
{
$matched = true;
}
else
{
$len = strlen($expected);
$result = strncmp($expected, $actual, $len);
if ($result == 0)
{
$matched = true;
}
}
// echo "Expected: $expected\nActual: $actual\n";
if ($matched)
{
if ($errorMode)
{
echo "Data corruption expected...\n";
}
}
else
{
if (! $errorMode)
{
echo "Data corruption!! Expected: $expected\nActual: $actual\n";
}
}
return $matched;
}
function CompareNumericData($actual, $expected)
{
if (! is_numeric($actual))
{
return false;
}
// only compare up to the length of the expected value, especially for floats
if (is_float($expected))
{
$len = strlen($expected);
$actual = substr($actual, 0, $len);
}
$f_actual = floatval($actual);
$f_expected = floatval($expected);
$matched = ($f_actual == $f_expected);
if (! $matched)
{
// compare floating point values
$diff = abs(($f_actual - $f_expected) / $f_expected);
$matched = ($diff < _EPSILON);
if (! $matched)
{
echo "Data corruption: values don't match\n";
}
}
return $matched;
}
function CheckData($colType, $actual, $expected)
{
$success = true;
if (IsNumericType($colType))
{
if (! CompareNumericData($actual, $expected))
{
$success = false;
}
}
else
{
$actual = trim($actual);
$len = strlen($expected);
if (IsDateTimeType($colType))
{
if ($colType != _SQL_SS_TIME2)
$len = min(strlen("YYYY-MM-DD HH:mm:ss"), $len);
else
$len = min(strlen("HH:mm:ss"), $len);
}
if (strncasecmp($actual, $expected, $len) != 0)
{
$success = false;
}
}
return ($success);
}
function ReadBinaryStream($filename)
{
$handle = fopen($filename, "rb");
$contents = fread($handle, filesize($filename));
fclose($handle);
return $contents;
}
function ToByteArray($input)
{
// this should be int[] which can be converted
// to byte[] in C# since values are range of 0 - 255
return unpack('C*', $input);
}
function CompareByteArrays($actual, $expected)
{
$actualArray = ToByteArray($actual);
$expectedArray = ToByteArray($expected);
echo "Data read:\t";
print_r($actualArray);
echo "Expected:\t";
print_r($expectedArray);
}
function CompareDataStream($colType, $rowIndex, $colName, $stream, $expected)
{
$len = strlen($expected);
$matched = false;
if ($len == 0)
{
$contents = fread($stream, _CHUNK_SIZE);
$contents = trim($contents); // removes all whitespace
if (strlen($contents) == 0)
{
$matched = true;
}
}
else if ($len < _CHUNK_SIZE)
{
$contents = fread($stream, $len);
$matched = CompareDataValue($colType, $rowIndex, $colName, $contents, $expected);
}
else
{
$matched = true;
$pos = 0;
while (! feof($stream) && $pos < $len)
{
$contents = fread($stream, _CHUNK_SIZE);
$contents = ConvertDataToString($colType, $contents);
$contents_len = strlen($contents);
$result = substr_compare($expected, $contents, $pos, $contents_len, TRUE);
if ($result != 0)
{
break;
$matched = false;
}
$pos += $contents_len;
}
}
if (! $matched)
{
echo "Data corruption on row $rowIndex column $colName\n";
}
return $matched;
}
function CompareBinaryData($actual, $expected)
{
// this function assumes $actual is a stream of hex
$len = strlen($expected);
$pos = 0;
$matched = true;
while (! feof($actual) && $pos < $len)
{
$contents = fread($actual, _CHUNK_SIZE);
$result = 0;
$str = unpack("H*", $contents);
$contents = $str[1];
$contents_len = strlen($contents);
$count = $contents_len;
if ($len < ($pos + $contents_len))
{
$count = $len - $pos;
}
$result = substr_compare($expected, $contents, $pos, $count, TRUE);
if ($result != 0)
{
//echo "Expected: " . substr($expected, $pos, $count) . "\nActual: $contents\n";
$matched = false;
break;
}
$pos += $count;
}
return $matched;
}
function CompareStreamData($actual, $expected, $IsUnicodeType)
{
// this function assumes $actual is a stream of character data
$matched = true;
// convert $expected to UCS-2LE (Little Endian)
$str = iconv("UTF-8", "UCS-2LE", $expected);
// every character of $str consists of two bytes but if
// it's unicode, the character of data is also two bytes
$shift = 1;
if ($IsUnicodeType)
$shift = 2;
$len = strlen($str);
$pos = 0;
while ($matched && ! feof($actual) && $pos < $len)
{
$contents = fread($actual, _CHUNK_SIZE);
$contents_len = strlen($contents);
$i = 0;
while ($matched && $i < $contents_len && $pos < $len)
{
if ($contents[$i] != $str[$pos])
{
//echo "Expected at [$pos]: $str[$pos]\nActual at [$i]: $contents[$i]\n";
$matched = false;
break;
}
$pos += 2;
$i += $shift;
}
}
return $matched;
}
function CompareBinaryStream($metadata, $rowIndex, $colIndex, $actual, $expected)
{
// unlike CompareDataStream(), this method assumes $actual is a stream
// and no non-updatable fields (timestamp column)
if (is_null($actual))
{
if ($expected === "")
return true;
else
return false;
}
$colName = $metadata[$colIndex]['Name'];
$colType = $metadata[$colIndex]['Type'];
$matched = false;
if (IsBinaryType($colType))
{
$matched = CompareBinaryData($actual, $expected);
}
else
{
// stream of characters
$matched = CompareStreamData($actual, $expected, IsUnicodeType($colType));
}
if (! $matched)
{
echo "Data not matching on row $rowIndex column $colName\n";
}
return $matched;
}
function CompareStringToFile($rowIndex, $colName, $actual, $file, $IsBinaryType)
{
$matched = true;
$pos = 0;
while ($matched && ! feof($file))
{
$expected = fread($file, _CHUNK_SIZE);
if ($IsBinaryType)
{
$str = unpack("H*", $expected);
$expected = $str[1];
}
$len = strlen($expected);
$contents = substr($actual, $pos, $len);
$result = strcasecmp($expected, $contents);
$matched = ($result == 0);
$pos += $len;
}
if (! $matched)
{
echo "Data not matching on row $rowIndex column $colName\n";
}
return $matched;
}
function CompareStreamToFile($rowIndex, $colName, $stream, $file, $colType)
{
// This function is complicated because it's comparing two stream data
// one from stream resource and one from a file
// Moreover, the data can be binary or character/unicode character
$IsBinaryType = IsBinaryType($colType);
$matched = true;
while ($matched && ! feof($file) && ! feof ($stream))
{
$expected = fread($file, _CHUNK_SIZE);
$actual = fread($stream, _CHUNK_SIZE);
if ($IsBinaryType)
{
$str = unpack("H*", $expected);
$expected = $str[1];
$str = unpack("H*", $actual);
$actual = $str[1];
$matched = ($expected == $actual);
}
else
{
// not reading binary hex data here
// similar to CompareStreamData(), except comparing two chunks at a time
// first convert $expected to UCS-2LE (Little Endian)
$expected = iconv("UTF-8", "UCS-2LE", $expected);
// every character of $expected consists of two bytes but if
// it's unicode, the character of data is also two bytes
$shift = 1;
if (IsUnicodeType($colType))
$shift = 2;
$i = 0;
$pos = 0;
$data_len = strlen($actual);
while ($matched && $i < $data_len)
{
if ($actual[$i] != $expected[$pos])
{
echo "Expected at [$pos]: $expected[$pos]\nActual at [$i]: $actual[$i]\n";
$matched = false;
break;
}
$pos += 2;
$i += $shift;
}
}
}
if (! $matched)
{
echo "Data not matching on row $rowIndex column $colName\n";
}
return $matched;
}
function CompareDataValue($colType, $rowIndex, $colName, $actual, $expected, $bConvert = true)
{
if ($bConvert)
{
$actual = ConvertDataToString($colType, $actual);
}
$matched = CheckData($colType, $actual, $expected);
if (! $matched)
{
echo "Data corruption on row $rowIndex column $colName\n";
echo "Expected: $expected\nActual: $actual\n";
}
return $matched;
}
function CompareData($metadata, $rowIndex, $colIndex, $actual, $expected, $isStream, $bConvert = true)
{
if (is_null($actual))
{
if ($expected === "")
return true;
else
return false;
}
$colName = $metadata[$colIndex]['Name'];
$colType = $metadata[$colIndex]['Type'];
if ($actual === false)
{
FatalError("Field $colName of row $rowIndex is missing\n");
}
if (! IsColumnUpdatable($colName))
{
return true; // do nothing for non-IsColumnUpdatable fields
}
if ($isStream)
{
return CompareDataStream($colType, $rowIndex, $colName, $actual, $expected);
}
else
{
return CompareDataValue($colType, $rowIndex, $colName, $actual, $expected, $bConvert);
}
}
function CompareLOBToFile($rowIndex, $colType, $colName, $actual, $filename, $readStream)
{
$IsBinaryType = IsBinaryType($colType);
$mode = ($IsBinaryType)? "rb" : "r";
$file = fopen($filename, $mode);
echo "...reading LOB data from '$filename'...";
$matched = false;
if ($readStream)
{
$matched = CompareStreamToFile($rowIndex, $colName, $actual, $file, $colType);
}
else
{
$matched = CompareStringToFile($rowIndex, $colName, $actual, $file, $IsBinaryType);
}
fclose($file);
return $matched;
}
function VerifyLOBData($metadata, $rowIndex, $colIndex, $lobColumn, $actual, $expected, $filename, $readStream)
{
$colName = $metadata[$colIndex]['Name'];
$colType = $metadata[$colIndex]['Type'];
$matched = false;
if ($readStream && IsStreamableType($colType))
{
if ($colIndex != $lobColumn)
{
echo "...reading binary stream...";
$matched = CompareBinaryStream($metadata, $rowIndex, $colIndex, $actual, $expected);
}
else
{
$matched = CompareLOBToFile($rowIndex, $colType, $colName, $actual, $filename, true);
}
}
else
{
if ($colIndex != $lobColumn)
{
echo "...reading string value...";
$matched = CompareDataValue($colType, $rowIndex, $colName, $actual, $expected, false);
}
else
{
$matched = CompareLOBToFile($rowIndex, $colType, $colName, $actual, $filename, false);
}
}
return $matched;
}
function SimplifyDataArray($data, $metadata, $row)
{
$dataArray = array();
$numFields = count($metadata);
$skipCount = 0;
for ($j = 0; $j < $numFields; $j++)
{
$colName = $metadata[$j]['Name'];
$colType = $metadata[$j]['Type'];
if (!IsColumnUpdatable($colName))
{
$skipCount++;
array_push($dataArray, "");
}
else
{
// only need the first element of each array
$value = $data[$j - $skipCount][0];
if (is_a($value, 'DateTime'))
{
$value = date_format( $value, 'Y-m-d H:i:s' );
}
array_push($dataArray, $value);
}
}
return $dataArray;
}
function InsertDataToArray($query, $metadata, $row)
{
$dataArray = array();
$numFields = count($metadata);
$skipCount = 0;
for ($j = 0; $j < $numFields; $j++)
{
$colName = $metadata[$j]['Name'];
$type = $metadata[$j]['Type'];
$col = $j + 1;
if (!IsColumnUpdatable($colName))
{
$skipCount++;
array_push($dataArray, "");
}
else
{
$data = GetInsertDataByType($query, $type, $row, $col, $skipCount);
array_push($dataArray, $data);
}
}
return $dataArray;
}
function GetInsertDataByType($query, $colType, $rowIndex, $colIndex, $skip)
{
$data = strstr($query, "((");
$pos = 1;
if ($data === false)
{
die("Failed to retrieve data on row $rowIndex");
}
$data = substr($data, 2);
while ($pos < ($colIndex - $skip))
{
$data = strstr($data, ", (");
$pos++;
if ($data === false)
{
die("Failed to retrieve data on column $pos");
}
$data = substr($data, 3);
}
// Is it's XML type, we can't use the closing bracket as the next delimiter
// because a bracket can be part of the xml data, unless the data is null
$str = ")";
$pos = strpos($data, $str);
if ($pos === false)
{
die("Failed to isolate data on row $rowIndex, column $pos");
}
$tmp = substr($data, 0, $pos);
if ((strcasecmp($tmp, "null") == 0) || strlen($tmp) == 0)
{
$tmp = "";
}
else if (IsXmlType($colType))
{
$str = ">')";
$pos = strpos($data, $str);
$tmp = substr($data, 0, $pos + 2);
}
$data = $tmp;
if (IsDataUnicode($colType, $data)) // this includes unicode data type and XML data that is in Unicode
{ // N'data'
$data = substr($data, 2, strlen($data) - 3);
}
else if (IsLiteralType($colType))
{ // 'data'
$data = substr($data, 1, strlen($data) - 2);
}
else if (IsBinaryType($colType))
{ // 0xdata
$data = substr($data, 2);
}
return (trim($data));
}
function IsStreamableType($type)
{
switch ($type)
{
case _SQL_CHAR: // char
return true;
case _SQL_WCHAR: // nchar
return true;
case _SQL_VARCHAR: // varchar
return true;
case _SQL_WVARCHAR: // nvarchar
return true;
case _SQL_LONGVARCHAR: // text
return true;
case _SQL_WLONGVARCHAR: // ntext
return true;
case _SQL_BINARY: // binary
return true;
case _SQL_VARBINARY: // varbinary
return true;
case _SQL_LONGVARBINARY: // image
return true;
case _SQL_SS_XML: // xml
return true;
default:
break;
}
return (false);
}
function IsNumericType($type)
{
switch ($type)
{
case _SQL_INTEGER : // int
return true;
case _SQL_TINYINT : // tinyint
return true;
case _SQL_SMALLINT : // smallint
return true;
case _SQL_BIGINT : // bigint
return true;
case _SQL_BIT : // bit
return true;
case _SQL_FLOAT : // float
return true;
case _SQL_REAL : // real
return true;
case _SQL_DECIMAL : // decimal
return true;
case _SQL_NUMERIC : // numeric, money, smallmoney
return true;
default: break;
}
return (false);
}
function IsCharType($type)
{
switch ($type)
{
case _SQL_WCHAR: // nchar
return true;
case _SQL_VARCHAR: // varchar
return true;
case _SQL_WVARCHAR: // nvarchar
return true;
case _SQL_LONGVARCHAR: // text
return true;
case _SQL_WLONGVARCHAR: // ntext
return true;
case _SQL_SS_XML: // xml
return true;
default:
break;
}
return (false);
}
function IsBinaryType($type)
{
switch ($type)
{
case _SQL_BINARY: // binary
return true;
case _SQL_VARBINARY: // varbinary
return true;
case _SQL_LONGVARBINARY: // image
return true;
default:
break;
}
return (false);
}
function IsDateTimeType($type)
{
switch ($type)
{
case _SQL_TYPE_TIMESTAMP: // datetime, smalldatetime
return true;
case _SQL_TYPE_DATE: // date
return true;
case _SQL_SS_TIME2: // time
return true;
case _SQL_SS_TIMESTAMPOFFSET: // datetimeoffset
return true;
default:
break;
}
return (false);
}
function IsDataUnicode($colType, $data)
{
if (IsUnicodeType($colType))
return true;
// This input string may be an XML string in unicode (i.e. // N'<xmldata>...</xmldata>')
$letterN = 'N';
$index = strpos($data, $letterN);
// Note the use of ===. Simply == would not work as expected
// because the position of letterN 'N' may be the 0th (first) character
// and strpos will return false if not found.
if ($index === 0) {
return true;
}
return false;
}
function IsUnicodeType($type)
{
switch ($type)
{
case _SQL_WCHAR: // nchar
return true;
case _SQL_WVARCHAR: // nvarchar
return true;
case _SQL_WLONGVARCHAR: // ntext
return true;
default:
break;
}
return (false);
}
function IsXmlType($type)
{
return ($type == _SQL_SS_XML);
}
function IsColumnUpdatable($colName)
{
$pos = strpos($colName, "_");
$type = substr($colName, $pos + 1);
return (strcasecmp($type, "timestamp") != 0);
}
function IsLiteralType($type)
{
switch ($type)
{
case _SQL_CHAR: // char
return true;
case _SQL_WCHAR: // nchar
return true;
case _SQL_VARCHAR: // varchar
return true;
case _SQL_WVARCHAR: // nvarchar
return true;
case _SQL_LONGVARCHAR: // text
return true;
case _SQL_WLONGVARCHAR: // ntext
return true;
case _SQL_GUID: // uniqueidentifier
return true;
case _SQL_TYPE_TIMESTAMP: // datetime, smalldatetime
return true;
case _SQL_TYPE_DATE: // date
return true;
case _SQL_SS_TIME2: // time
return true;
case _SQL_SS_TIMESTAMPOFFSET: // datetimeoffset
return true;
case _SQL_SS_XML: // xml
return true;
default:
break;
}
return (false);
}
?>