1245 lines
40 KiB
PHP
1245 lines
40 KiB
PHP
<?php
|
|
/*
|
|
Microsoft SQL Server Driver for PHP - Unit Test Framework
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Description:
|
|
Helper classes and functions with Always Encrypted features incorporated.
|
|
|
|
*/
|
|
|
|
namespace AE {
|
|
|
|
require_once('MsSetup.inc');
|
|
|
|
const KEYSTORE_NONE = 'none';
|
|
const KEYSTORE_WIN = 'win';
|
|
const KEYSTORE_KSP = 'ksp';
|
|
const KEYSTORE_AKV = 'akv';
|
|
|
|
const INSERT_QUERY = 1;
|
|
const INSERT_PREPARE = 2;
|
|
const INSERT_QUERY_PARAMS = 3;
|
|
const INSERT_PREPARE_PARAMS = 4;
|
|
|
|
/**
|
|
* class for encapsulating column metadata needed for creating a table
|
|
*/
|
|
class ColumnMeta
|
|
{
|
|
public $dataType; // a string that includes the size of the type if necessary (e.g., decimal(10,5))
|
|
public $colName; // column name
|
|
public $encType; // randomized or deterministic; default is deterministic
|
|
public $options; // a string that is null by default (e.g. NOT NULL Identity (1,1) )
|
|
|
|
protected $encryptable; // whether Always Encrypted supports this column
|
|
protected $forcedEncrypt; // force column encryption regardless, default to 'false'
|
|
|
|
public function __construct($dataType, $colName = null, $options = null, $deterministic = true, $noEncrypt = false)
|
|
{
|
|
if (is_null($colName)) {
|
|
$this->colName = getDefaultColname($dataType);
|
|
} else {
|
|
$this->colName = $colName;
|
|
}
|
|
|
|
$this->forcedEncrypt = false;
|
|
|
|
$this->encType = ($deterministic ? "deterministic" : "randomized");
|
|
if (empty($dataType)) {
|
|
echo "Data type can not be null or empty!\n";
|
|
}
|
|
$this->dataType = $dataType;
|
|
$this->options = $options;
|
|
|
|
if (!$noEncrypt) {
|
|
$this->checkIfUnsupported();
|
|
} else {
|
|
$this->encryptable = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* if Always Encrypted not supported for this data type set $noEncryption to false
|
|
* @return void
|
|
*/
|
|
protected function checkIfUnsupported()
|
|
{
|
|
// Always Encrypted is not supported for columns with the IDENTITY property
|
|
// or any column using one of the following datatypes:
|
|
//
|
|
// xml, timestamp, image, ntext, text, sql_variant, hierarchyid, geography, geometry, alias,
|
|
// user defined-types.
|
|
// https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/always-encrypted-database-engine
|
|
|
|
$unsupported = array("xml", "timestamp", "image", "ntext", "text", "sql_variant", "hierarchyid", "geography", "geometry", "alias");
|
|
|
|
if (!is_null($this->options) && stripos($this->options, "identity") !== false) {
|
|
$this->encryptable = false;
|
|
} elseif (in_array(strtolower($this->dataType), $unsupported)) {
|
|
$this->encryptable = false;
|
|
} else {
|
|
$this->encryptable = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* force column to be encrypted regardless of the current settings
|
|
* @return void
|
|
*/
|
|
public function forceEncryption($forceEncryption)
|
|
{
|
|
$this->forcedEncrypt = $forceEncryption;
|
|
}
|
|
|
|
/**
|
|
* @return string column definition for creating a table
|
|
*/
|
|
public function getColDef()
|
|
{
|
|
$append = " ";
|
|
|
|
if (($this->encryptable && isDataEncrypted()) || $this->forcedEncrypt) {
|
|
|
|
$cekName = getCekName();
|
|
if ($this->forcedEncrypt && empty($cekName)) {
|
|
$cekName = 'AEColumnKey'; // Use Windows AE key by default
|
|
}
|
|
if (stripos($this->dataType, "char") !== false) {
|
|
$append .= "COLLATE Latin1_General_BIN2 ";
|
|
}
|
|
$append .= sprintf("ENCRYPTED WITH (ENCRYPTION_TYPE = %s, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = $cekName) ", $this->encType);
|
|
}
|
|
$append .= $this->options;
|
|
$colDef = "[" . $this->colName . "] " . $this->dataType . $append;
|
|
return $colDef;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* class for encapsulating optional parameters for binding parameters in sqlsrv_prepare
|
|
*/
|
|
class BindParamOption
|
|
{
|
|
public $value; // the param value
|
|
public $direction; // SQLSRV_PARAM_ constant indicating the parameter direction
|
|
public $phpType; // SQLSRV_PHPTYPE_ constant specifying the php type of the return values
|
|
public $sqlType; // SQLSRV_SQLTYPE_ constant specifying the SQL type of the input
|
|
|
|
public function __construct($value, $direction = null, $phpType = null, $sqlType = null)
|
|
{
|
|
$this->value = $value;
|
|
$this->direction = $direction;
|
|
$this->phpType = $phpType;
|
|
$this->sqlType = $sqlType;
|
|
}
|
|
|
|
/**
|
|
* @return array needed to bind parameter in sqlsrv_prepare
|
|
*/
|
|
public function bindParamArr()
|
|
{
|
|
// get the constant values of direction, phpType, and sqlType
|
|
$direction = null;
|
|
$phpType = null;
|
|
$sqlType = null;
|
|
if ($this->direction) {
|
|
if (in_array($this->direction, array(SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, SQLSRV_PARAM_INOUT))) {
|
|
$direction = constant($this->direction);
|
|
} else {
|
|
echo "BindParamOption: invalid direction for parameter!\n";
|
|
}
|
|
}
|
|
if ($this->phpType) {
|
|
try {
|
|
$arr = explode("(", $this->phpType);
|
|
$type = $arr[0];
|
|
if (count($arr) > 1) {
|
|
$enc = rtrim($arr[1], ")");
|
|
$phpType = call_user_func($type, constant($enc));
|
|
} else {
|
|
$phpType = constant($this->phpType);
|
|
}
|
|
} catch (Exception $e) {
|
|
// there's something wrong with the input php type
|
|
echo $e->getMessage();
|
|
}
|
|
}
|
|
if ($this->sqlType) {
|
|
// parse the datatype name, size, precision, and/or scale from a SQLSRV_SQLTYPE_ constant
|
|
$size = null;
|
|
$prec = null;
|
|
$scal = null;
|
|
$type_size = explode("(", $this->sqlType);
|
|
$type = $type_size[0];
|
|
if (count($type_size) > 1) {
|
|
$size = rtrim($type_size[1], ")");
|
|
$prec_scal = explode(",", $size);
|
|
if (count($prec_scal) > 1) {
|
|
$prec = $prec_scal[0];
|
|
$scal = $prec_scal[1];
|
|
$size = null;
|
|
}
|
|
if (!is_null($size) && strpos($size, "max") !== false) {
|
|
$size = trim($size, "'");
|
|
}
|
|
}
|
|
// get the sqlType constant
|
|
try {
|
|
if ($prec && $scal) {
|
|
$sqlType = call_user_func($type, $prec, $scal);
|
|
} elseif ($size) {
|
|
$sqlType = call_user_func($type, $size);
|
|
} else {
|
|
$sqlType = constant($type);
|
|
}
|
|
} catch (Exception $e) {
|
|
// there's something wrong with the input SQL type
|
|
echo $e->getMessage();
|
|
}
|
|
}
|
|
return array($this->value, $direction, $phpType, $sqlType);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return string CEK name depending on the connection keywords
|
|
*/
|
|
function getCekName()
|
|
{
|
|
global $keystore;
|
|
|
|
$cekName = '';
|
|
switch ($keystore) {
|
|
case KEYSTORE_NONE:
|
|
$cekName = '';
|
|
break;
|
|
case KEYSTORE_WIN:
|
|
$cekName = 'AEColumnKey';
|
|
break;
|
|
case KEYSTORE_KSP:
|
|
$cekName = 'CustomCEK';
|
|
break;
|
|
case KEYSTORE_AKV:
|
|
$cekName = 'AKVColumnKey';
|
|
break;
|
|
default:
|
|
echo "getCekName: Invalid keystore name.\n";
|
|
}
|
|
return $cekName;
|
|
}
|
|
|
|
/**
|
|
* @return string default column name when a name is not provided in the ColumnMeta class
|
|
*/
|
|
function getDefaultColname($dataType)
|
|
{
|
|
$colName = "c_" . str_replace(",", "_", str_replace("(", "_", $dataType));
|
|
$colName = rtrim($colName, ")");
|
|
return $colName;
|
|
}
|
|
|
|
/**
|
|
* @param string $tbname : name of the table for an insert sql
|
|
* @param array $input : associative array containing a key value pair of column name and data to put into an insert sql string
|
|
* @return string a complete insert sql string
|
|
*/
|
|
function getInsertSqlComplete($tbname, $inputs)
|
|
{
|
|
$colStr = "INSERT INTO [$tbname] (";
|
|
$valStr = "VALUES (";
|
|
if (empty($inputs)) {
|
|
echo "getInsertSqlComplete: inputs for inserting a row cannot be empty.\n";
|
|
return;
|
|
}
|
|
foreach ($inputs as $key => $value) {
|
|
$colStr .= $key . ", ";
|
|
if (is_null($value)) {
|
|
$valStr .= "null, ";
|
|
} elseif (is_string($value)) {
|
|
$valStr .= "'" . $value . "', ";
|
|
} else {
|
|
$valStr .= $value . ", ";
|
|
}
|
|
}
|
|
$colStr = rtrim($colStr, ", ") . ") ";
|
|
$valStr = rtrim($valStr, ", ") . ") ";
|
|
$insertSql = $colStr . $valStr;
|
|
return $insertSql;
|
|
}
|
|
|
|
/**
|
|
* @param string $tbname : name of the table for an insert sql
|
|
* @param array $inputs : associative array containing a key value pair of column name and data to put into an insert sql string
|
|
* @return string an insert sql string with "?" placeholders for all values
|
|
*/
|
|
function getInsertSqlPlaceholders($tbname, $inputs)
|
|
{
|
|
$colStr = "INSERT INTO [$tbname] (";
|
|
$valStr = "VALUES (";
|
|
if (empty($inputs)) {
|
|
echo "getInsertSqlPlaceholders: inputs for inserting a row cannot be empty.\n";
|
|
return;
|
|
}
|
|
foreach ($inputs as $key => $value) {
|
|
$colStr .= $key . ", ";
|
|
}
|
|
$colStr = rtrim($colStr, ", ") . ") ";
|
|
$valStr .= getSeqPlaceholders(count($inputs)) . ") ";
|
|
$insertSql = $colStr . $valStr;
|
|
return $insertSql;
|
|
}
|
|
|
|
/**
|
|
* @param string $spname : name of the stored procedure
|
|
* @param int $num : number of parameters needed for the stored procedure
|
|
* @return string a call stored procedure sql string with "?" placeholders for all parameters
|
|
*/
|
|
function getCallProcSqlPlaceholders($spname, $num)
|
|
{
|
|
$callStr = "{CALL [$spname] (";
|
|
$callStr .= getSeqPlaceholders($num) . ")} ";
|
|
return $callStr;
|
|
}
|
|
|
|
/**
|
|
* @param int $num : number of placeholders needed
|
|
* @return string a string containing $num number of repeated "?" placeholders delimited by ", "
|
|
*/
|
|
function getSeqPlaceholders($num)
|
|
{
|
|
if ($num <= 0) {
|
|
echo "getSeqPlaceholders: num provided for creating a sequence of placeholders cannot be less than 0.\n";
|
|
return;
|
|
}
|
|
$placeholderStr = str_repeat("?, ", $num);
|
|
$placeholderStr = rtrim($placeholderStr, ", ");
|
|
return $placeholderStr;
|
|
}
|
|
|
|
/**
|
|
* @return bool false if $keystore specified in MsSetup.inc is none; return true otherwise
|
|
*/
|
|
function isColEncrypted()
|
|
{
|
|
global $keystore;
|
|
return ($keystore !== KEYSTORE_NONE);
|
|
}
|
|
|
|
/**
|
|
* @return bool false if $keystore specified in MsSetup.inc is none or data not encrypted;
|
|
* return true otherwise
|
|
*/
|
|
function isDataEncrypted()
|
|
{
|
|
global $keystore, $dataEncrypted;
|
|
return ($keystore !== KEYSTORE_NONE && $dataEncrypted);
|
|
}
|
|
|
|
/**
|
|
* @return bool false if ODBC version is less than 17 or SQL Server older than 2016
|
|
*/
|
|
function isQualified($conn)
|
|
{
|
|
$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer'];
|
|
if (explode(".", $msodbcsql_ver)[0] < 17) {
|
|
return false;
|
|
}
|
|
global $daasMode;
|
|
if ($daasMode) {
|
|
// running against Azure
|
|
return true;
|
|
}
|
|
// if not Azure, check the server version
|
|
$server_ver = sqlsrv_server_info($conn)['SQLServerVersion'];
|
|
if (explode('.', $server_ver)[0] < 13) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Connect to the database specified in MsSetup.inc; Column Encryption options automatically added when $keystore is not none
|
|
* @param array $options : connection attributes to pass to sqlsrv_connect
|
|
* @param bool $disableCE : flag for disabling column encryption even when keystore is NOT none
|
|
* for testing fetching encrypted data when connection column encryption is off
|
|
* @return connection resource
|
|
*/
|
|
function connect($options = array(), $disableCE = false)
|
|
{
|
|
require('MsSetup.inc');
|
|
if (!empty($options)) {
|
|
$connectionOptions = array_merge($connectionOptions, $options);
|
|
}
|
|
if (!$disableCE) {
|
|
if (isColEncrypted()) {
|
|
$connectionOptions = array_merge($connectionOptions, array("ColumnEncryption" => "Enabled"));
|
|
}
|
|
if ($keystore == 'akv') {
|
|
$akv_options = array("KeyStoreAuthentication"=>$AKVKeyStoreAuthentication);
|
|
if ($AKVKeyStoreAuthentication == "KeyVaultPassword") {
|
|
$akv_options["KeyStorePrincipalId"] = $AKVPrincipalName;
|
|
$akv_options["KeyStoreSecret"] = $AKVPassword;
|
|
} else if ($AKVKeyStoreAuthentication == "KeyVaultClientSecret") {
|
|
$akv_options["KeyStorePrincipalId"] = $AKVClientID;
|
|
$akv_options["KeyStoreSecret"] = $AKVSecret;
|
|
}
|
|
$connectionOptions = array_merge($connectionOptions, $akv_options);
|
|
}
|
|
}
|
|
$conn = sqlsrv_connect($server, $connectionOptions);
|
|
if ($conn === false) {
|
|
fatalError("Failed to connect to $server.");
|
|
}
|
|
return $conn;
|
|
}
|
|
|
|
/**
|
|
* Create a table
|
|
* @param resource $conn : sqlsrv connection resource
|
|
* @param string $tbname : name of the table to be created
|
|
* @param array $columnMetaArr : array of ColumnMeta objects, which contain metadata for one column
|
|
* @return resource sqlsrv statement resource
|
|
*/
|
|
function createTable($conn, $tbname, $columnMetaArr)
|
|
{
|
|
require_once("MsCommon.inc");
|
|
dropTable($conn, $tbname);
|
|
$colDef = "";
|
|
foreach ($columnMetaArr as $meta) {
|
|
$colDef = $colDef . $meta->getColDef() . ", ";
|
|
}
|
|
$colDef = rtrim($colDef, ", ");
|
|
$createSql = "CREATE TABLE [$tbname] ( $colDef )";
|
|
return sqlsrv_query($conn, $createSql);
|
|
}
|
|
|
|
/**
|
|
* Insert a row into a table
|
|
* @param resource $conn : sqlsrv connection resource
|
|
* @param string $tbname : name of the table for the row to be inserted
|
|
* @param array $inputs : an associative array column name and its value, which may be a
|
|
* literal or a BindParamOption object
|
|
* @param bool $r : true if the row was successfully inserted, otherwise false. Default value is null to make this parameter optional.
|
|
* $param int $api : SQLSRV API used for executing the insert query
|
|
* accepted values: INSERT_QUERY, INSERT_PREPARE, INSERT_QUERY_PARAMS, INSERT_PREPARE_PARAMS
|
|
* @return resource sqlsrv statement resource of the insert statement
|
|
*/
|
|
function insertRow($conn, $tbname, $inputs, &$r = null, $api = INSERT_QUERY)
|
|
{
|
|
$stmt = null;
|
|
if (!isColEncrypted() && $api < INSERT_QUERY_PARAMS) {
|
|
$insertSql = getInsertSqlComplete($tbname, $inputs);
|
|
switch ($api) {
|
|
case INSERT_QUERY:
|
|
$stmt = sqlsrv_query($conn, $insertSql);
|
|
break;
|
|
case INSERT_PREPARE:
|
|
$stmt = sqlsrv_prepare($conn, $insertSql);
|
|
if ($stmt) {
|
|
$r = sqlsrv_execute($stmt);
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
// must bind param
|
|
$insertSql = getInsertSqlPlaceholders($tbname, $inputs);
|
|
$params = array();
|
|
foreach ($inputs as $key => $input) {
|
|
if (is_object($input)) {
|
|
array_push($params, $input->bindParamArr());
|
|
} else {
|
|
array_push($params, $inputs[$key]);
|
|
}
|
|
}
|
|
|
|
// use prepare for inserts when AE is enabled
|
|
if (isColEncrypted() || $api == INSERT_PREPARE_PARAMS) {
|
|
$stmt = sqlsrv_prepare($conn, $insertSql, $params);
|
|
if ($stmt) {
|
|
$r = sqlsrv_execute($stmt);
|
|
} else {
|
|
fatalError("insertRow: failed to prepare insert query!");
|
|
}
|
|
} else {
|
|
$stmt = sqlsrv_query($conn, $insertSql, $params);
|
|
}
|
|
}
|
|
|
|
return $stmt;
|
|
}
|
|
|
|
/**
|
|
* Perform a simple select from a table with or without where condition(s)
|
|
* @param resource $conn : connection resource
|
|
* @param string $tbname : name of the table
|
|
* @param string $conds : string of condition(s) with placeholders, null by default
|
|
* @param array $values : array of parameters, null by default
|
|
* @return resource sqlsrv statement upon success or false otherwise
|
|
*/
|
|
function selectFromTable($conn, $tbname, $conds = null, $values = null)
|
|
{
|
|
$sql = "SELECT * FROM $tbname";
|
|
return executeQuery($conn, $sql, $conds, $values);
|
|
}
|
|
|
|
/**
|
|
* Perform a query from a table with or without where condition(s)
|
|
* @param resource $conn : connection resource
|
|
* @param string $sql : T-SQL query
|
|
* @param string $conds : string of condition(s) possibly with placeholders, null by default
|
|
* @param array $values : array of parameters for placeholders in $conds, null by default
|
|
* @param array $options : array of query options, null by default
|
|
* @return resource sqlsrv statement upon success or false otherwise
|
|
*/
|
|
function executeQuery($conn, $sql, $conds = null, $values = null, $options = null)
|
|
{
|
|
if (!isColEncrypted()) {
|
|
// if not encrypted replace placeholders ('?') with values, if array not empty
|
|
if (!empty($values)) {
|
|
$tmpArray = explode('?', $conds);
|
|
$i = 0;
|
|
foreach ($values as $value) {
|
|
if (!is_numeric($value)) {
|
|
$tmpArray[$i++] .= "'$value'";
|
|
} else {
|
|
$tmpArray[$i++] .= "$value";
|
|
}
|
|
}
|
|
$clause = implode($tmpArray);
|
|
$sql = $sql . " WHERE $clause ";
|
|
} elseif (!empty($conds)) {
|
|
$sql = $sql . " WHERE $conds ";
|
|
}
|
|
|
|
$stmt = sqlsrv_query($conn, $sql, null, $options);
|
|
} else {
|
|
// with AE enabled, use sqlsrv_prepare() in case there are
|
|
// fields with unlimited size
|
|
if (empty($conds)) {
|
|
$stmt = sqlsrv_prepare($conn, $sql, null, $options);
|
|
} else {
|
|
$sql = $sql . " WHERE $conds ";
|
|
// pass $values to sqlsrv_prepare whether the array is null, empty or filled
|
|
$stmt = sqlsrv_prepare($conn, $sql, $values, $options);
|
|
}
|
|
if ($stmt) {
|
|
$r = sqlsrv_execute($stmt);
|
|
if (!$r) {
|
|
fatalError("executeQuery: failed to execute \'$sql\'!");
|
|
}
|
|
}
|
|
}
|
|
if (!$stmt) {
|
|
fatalError("executeQuery: failed to run query \'$sql\'!");
|
|
}
|
|
|
|
return $stmt;
|
|
}
|
|
|
|
/**
|
|
* Similar to executeQuery() but with query options
|
|
* @return resource sqlsrv statement upon success or false otherwise
|
|
*/
|
|
function executeQueryEx($conn, $sql, $options)
|
|
{
|
|
// when AE is enabled, use sqlsrv_prepare() to handle fields with unlimited size
|
|
if (!isColEncrypted()) {
|
|
$stmt = sqlsrv_query($conn, $sql, null, $options);
|
|
} else {
|
|
$stmt = sqlsrv_prepare($conn, $sql, null, $options);
|
|
if ($stmt) {
|
|
$r = sqlsrv_execute($stmt);
|
|
if (!$r) {
|
|
fatalError("executeQueryEx: failed to execute \'$sql\'!");
|
|
}
|
|
}
|
|
}
|
|
if (!$stmt) {
|
|
fatalError("executeQueryEx: failed to run query \'$sql\'!");
|
|
}
|
|
|
|
return $stmt;
|
|
}
|
|
|
|
/**
|
|
* Similar to executeQuery() but with params for binding and
|
|
* whether this query is expected to fail
|
|
* @return resource sqlsrv statement upon success or false otherwise
|
|
*/
|
|
function executeQueryParams($conn, $sql, $params, $expectedToFail = false, $message = '')
|
|
{
|
|
$res = true;
|
|
if (isColEncrypted()) {
|
|
$stmt = sqlsrv_prepare($conn, $sql, $params);
|
|
if ($stmt) {
|
|
$res = sqlsrv_execute($stmt);
|
|
}
|
|
} else {
|
|
$stmt = sqlsrv_query($conn, $sql, $params);
|
|
}
|
|
|
|
if ($stmt === false || !$res ) {
|
|
if (! $expectedToFail) {
|
|
fatalError($message);
|
|
} else {
|
|
print_r(sqlsrv_errors());
|
|
}
|
|
} else {
|
|
if ($expectedToFail) {
|
|
fatalError($message);
|
|
}
|
|
}
|
|
|
|
return $stmt;
|
|
}
|
|
|
|
/**
|
|
* Fetch all rows and all columns given a table name, and print them
|
|
* @param resource $conn : connection resource
|
|
* @param string $tbname : name of the table to fetch from
|
|
*/
|
|
function fetchAll($conn, $tbname)
|
|
{
|
|
$stmt = selectFromTable($conn, $tbname);
|
|
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
|
foreach ($row as $key => $value) {
|
|
if (is_object($value)) {
|
|
print "$key:\n";
|
|
foreach ($value as $k =>$v) {
|
|
print(" $k: $v\n");
|
|
}
|
|
} else {
|
|
print("$key: $value\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a test table with columns of various types given a table name,
|
|
* all deterministic if AE is enabled
|
|
* @param resource $conn : connection resource
|
|
* @param string $tbname : name of the table to create
|
|
* @return resource sqlsrv statement
|
|
*/
|
|
function createTestTable($conn, $tbname)
|
|
{
|
|
$columns = array(new ColumnMeta('int', 'c1_int'),
|
|
new ColumnMeta('tinyint', 'c2_tinyint'),
|
|
new ColumnMeta('smallint', 'c3_smallint'),
|
|
new ColumnMeta('bigint', 'c4_bigint'),
|
|
new ColumnMeta('bit', 'c5_bit'),
|
|
new ColumnMeta('float', 'c6_float'),
|
|
new ColumnMeta('real', 'c7_real'),
|
|
new ColumnMeta('decimal(28,4)', 'c8_decimal'),
|
|
new ColumnMeta('numeric(32,4)', 'c9_numeric'),
|
|
new ColumnMeta('money', 'c10_money', null, true, true),
|
|
new ColumnMeta('smallmoney', 'c11_smallmoney', null, true, true),
|
|
new ColumnMeta('char(512)', 'c12_char'),
|
|
new ColumnMeta('varchar(512)', 'c13_varchar'),
|
|
new ColumnMeta('varchar(max)', 'c14_varchar_max'),
|
|
new ColumnMeta('nchar(512)', 'c15_nchar'),
|
|
new ColumnMeta('nvarchar(512)', 'c16_nvarchar'),
|
|
new ColumnMeta('nvarchar(max)', 'c17_nvarchar_max'),
|
|
new ColumnMeta('text', 'c18_text'),
|
|
new ColumnMeta('ntext', 'c19_ntext'),
|
|
new ColumnMeta('binary(512)', 'c20_binary'),
|
|
new ColumnMeta('varbinary(512)', 'c21_varbinary'),
|
|
new ColumnMeta('varbinary(max)', 'c22_varbinary_max'),
|
|
new ColumnMeta('image', 'c23_image'),
|
|
new ColumnMeta('uniqueidentifier', 'c24_uniqueidentifier'),
|
|
new ColumnMeta('datetime', 'c25_datetime'),
|
|
new ColumnMeta('smalldatetime', 'c26_smalldatetime'),
|
|
new ColumnMeta('timestamp', 'c27_timestamp'),
|
|
new ColumnMeta('xml', 'c28_xml'),
|
|
);
|
|
|
|
return createTable($conn, $tbname, $columns);
|
|
}
|
|
|
|
/**
|
|
* Insert the specified number of rows with test data into the test table
|
|
* @param resource $conn : connection resource
|
|
* @param string $tbname : name of the table to create
|
|
* @param int $rowCount : number of test rows to be inserted
|
|
* @return number of rows inserted
|
|
*/
|
|
function insertTestRows($conn, $tbame, $rowCount)
|
|
{
|
|
$count = 0;
|
|
for ($i = 0; $i < $rowCount; $i++) {
|
|
if (insertTestRow($conn, $tbame, rand(1, 20))) {
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
if ($count != $rowCount) {
|
|
die("$count rows inserted instead of $rowCount\n");
|
|
}
|
|
return ($count);
|
|
}
|
|
|
|
/**
|
|
* Insert a number of rows with test data into the test table given the indexes
|
|
* @param resource $conn : connection resource
|
|
* @param string $tbname : name of the table to create
|
|
* @param int $minIndex : index of the first row to be inserted
|
|
* @param int $maxIndex : index of the last row to be inserted
|
|
* @return number of rows inserted
|
|
*/
|
|
function insertTestRowsByRange($conn, $tbame, $minIndex, $maxIndex)
|
|
{
|
|
$count = 0;
|
|
$rowCount = $maxIndex - $minIndex + 1;
|
|
if ($rowCount > 0) {
|
|
for ($i = $minIndex; $i <= $maxIndex; $i++) {
|
|
if (insertTestRow($conn, $tbame, $i)) {
|
|
$count++;
|
|
}
|
|
}
|
|
}
|
|
if ($count != $rowCount) {
|
|
die("$count rows inserted instead of $rowCount\n");
|
|
}
|
|
return ($count);
|
|
}
|
|
|
|
/**
|
|
* Insert one or more specific rows of test data into the test table
|
|
* @param resource $conn : connection resource
|
|
* @param string $tbname : name of the table to create
|
|
* @param int $index : the index of a certain row of test data
|
|
* @return resource sqlsrv statement upon success or false otherwise
|
|
*/
|
|
function insertTestRow($conn, $tbname, $index)
|
|
{
|
|
if ($index < 1 || $index > 20) {
|
|
echo("Invalid row index $index for test data!\n");
|
|
return false;
|
|
}
|
|
// get array of input values
|
|
$inputArray = getInsertArray($index);
|
|
if (empty($inputArray)) {
|
|
fatalError("getInsertSqlComplete: inputs for inserting a row cannot be empty");
|
|
}
|
|
|
|
$result = null;
|
|
if (isColEncrypted()) {
|
|
$stmt = insertRow($conn, $tbname, $inputArray, $result);
|
|
} else {
|
|
// do not use the generic insertRow as the binary data needs some pre-processing
|
|
// before calling sqlsrv_query()
|
|
$colStr = "INSERT INTO $tbname (";
|
|
$valStr = "VALUES (";
|
|
$col = 1;
|
|
foreach ($inputArray as $key => $value) {
|
|
$colStr .= $key . ", ";
|
|
|
|
if (is_array($value)) {
|
|
$value = $value[0];
|
|
// this might be an input to a decimal, a numeric or a binary field
|
|
if (isBinary($col)) {
|
|
if (!is_null($value)) {
|
|
$value = "0x" . $value; // annotate the input string as a hex string
|
|
} else {
|
|
$value = null;
|
|
}
|
|
}
|
|
}
|
|
if (is_null($value)) {
|
|
$valStr .= "null, ";
|
|
} elseif (is_string($value) && !isBinary($col)) {
|
|
$valStr .= "'" . $value . "', ";
|
|
} else {
|
|
$valStr .= $value . ", ";
|
|
}
|
|
$col++;
|
|
}
|
|
$colStr = rtrim($colStr, ", ") . ") ";
|
|
$valStr = rtrim($valStr, ", ") . ") ";
|
|
$insertSql = $colStr . $valStr;
|
|
$stmt = sqlsrv_query($conn, $insertSql);
|
|
}
|
|
if (!$stmt || $result === false) {
|
|
fatalError("insertTestRow: failed to insert row $index!\n");
|
|
}
|
|
return $stmt;
|
|
}
|
|
|
|
/**
|
|
* Get a row of data to be inserted into the test table
|
|
* @param int $index : the index of a certain row of test data
|
|
* @return an array of key-value pairs
|
|
*/
|
|
function getInsertArray($index)
|
|
{
|
|
if (! UseUTF8data()) {
|
|
require_once('MsData.inc');
|
|
return getTestData($index);
|
|
} else {
|
|
require_once('MsData_UTF8.inc');
|
|
return getTestData_UTF8($index);
|
|
}
|
|
|
|
// get array of input values
|
|
}
|
|
|
|
/**
|
|
* Get test data based on the row and column
|
|
* @param int $rowIndex : the row index
|
|
* @param int $colIndex : the column index
|
|
* @return data of mixed type or null if something went wrong
|
|
*/
|
|
function getInsertData($rowIndex, $colIndex)
|
|
{
|
|
// get array of input values
|
|
$inputArray = getInsertArray($rowIndex);
|
|
if (empty($inputArray)) {
|
|
fatalError("getInsertData: failed to retrieve data at row $rowIndex.\n");
|
|
}
|
|
|
|
$key = getColName($colIndex);
|
|
if (!empty($key)) {
|
|
$value = $inputArray[$key];
|
|
if (is_array($value)) {
|
|
return $value[0];
|
|
} else {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
} // end of namespace AlwaysEncrypted
|
|
|
|
namespace {
|
|
function getSqlType($k)
|
|
{
|
|
switch ($k) {
|
|
case 1: return ("int");
|
|
case 2: return ("tinyint");
|
|
case 3: return ("smallint");
|
|
case 4: return ("bigint");
|
|
case 5: return ("bit");
|
|
case 6: return ("float");
|
|
case 7: return ("real");
|
|
case 8: return ("decimal(28,4)");
|
|
case 9: return ("numeric(32,4)");
|
|
case 10: return ("money");
|
|
case 11: return ("smallmoney");
|
|
case 12: return ("char(512)");
|
|
case 13: return ("varchar(512)");
|
|
case 14: return ("varchar(max)");
|
|
case 15: return ("nchar(512)");
|
|
case 16: return ("nvarchar(512)");
|
|
case 17: return ("nvarchar(max)");
|
|
case 18: return ("text");
|
|
case 19: return ("ntext");
|
|
case 20: return ("binary(512)");
|
|
case 21: return ("varbinary(512)");
|
|
case 22: return ("varbinary(max)");
|
|
case 23: return ("image");
|
|
case 24: return ("uniqueidentifier");
|
|
case 25: return ("datetime");
|
|
case 26: return ("smalldatetime");
|
|
case 27: return ("timestamp");
|
|
case 28: return ("xml");
|
|
default: break;
|
|
}
|
|
return ("udt");
|
|
}
|
|
|
|
function getSqlsrvSqlType($k, $dataSize)
|
|
{
|
|
switch ($k) {
|
|
case 1: return (SQLSRV_SQLTYPE_INT);
|
|
case 2: return (SQLSRV_SQLTYPE_TINYINT);
|
|
case 3: return (SQLSRV_SQLTYPE_SMALLINT);
|
|
case 4: return (SQLSRV_SQLTYPE_BIGINT);
|
|
case 5: return (SQLSRV_SQLTYPE_BIT);
|
|
case 6: return (SQLSRV_SQLTYPE_FLOAT);
|
|
case 7: return (SQLSRV_SQLTYPE_REAL);
|
|
case 8: return (SQLSRV_SQLTYPE_DECIMAL(28, 4));
|
|
case 9: return (SQLSRV_SQLTYPE_NUMERIC(32, 4));
|
|
case 10: return (SQLSRV_SQLTYPE_MONEY);
|
|
case 11: return (SQLSRV_SQLTYPE_SMALLMONEY);
|
|
case 12: return (SQLSRV_SQLTYPE_CHAR($dataSize));
|
|
case 13: return (SQLSRV_SQLTYPE_VARCHAR($dataSize));
|
|
case 14: return (SQLSRV_SQLTYPE_VARCHAR('max'));
|
|
case 15: return (SQLSRV_SQLTYPE_NCHAR($dataSize));
|
|
case 16: return (SQLSRV_SQLTYPE_NVARCHAR($dataSize));
|
|
case 17: return (SQLSRV_SQLTYPE_NVARCHAR('max'));
|
|
case 18: return (SQLSRV_SQLTYPE_TEXT);
|
|
case 19: return (SQLSRV_SQLTYPE_NTEXT);
|
|
case 20: return (SQLSRV_SQLTYPE_BINARY($dataSize));
|
|
case 21: return (SQLSRV_SQLTYPE_VARBINARY($dataSize));
|
|
case 22: return (SQLSRV_SQLTYPE_VARBINARY('max'));
|
|
case 23: return (SQLSRV_SQLTYPE_IMAGE);
|
|
case 24: return (SQLSRV_SQLTYPE_UNIQUEIDENTIFIER);
|
|
case 25: return (SQLSRV_SQLTYPE_DATETIME);
|
|
case 26: return (SQLSRV_SQLTYPE_SMALLDATETIME);
|
|
case 27: return (SQLSRV_SQLTYPE_TIMESTAMP);
|
|
case 28: return (SQLSRV_SQLTYPE_XML);
|
|
default: break;
|
|
}
|
|
return (SQLSRV_SQLTYPE_UDT);
|
|
}
|
|
|
|
function isXml($k)
|
|
{
|
|
switch ($k) {
|
|
case 28: return (true); // xml
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isStreamable($k)
|
|
{
|
|
switch ($k) {
|
|
case 12: return (true); // char(512)
|
|
case 13: return (true); // varchar(512)
|
|
case 14: return (true); // varchar(max)
|
|
case 15: return (true); // nchar(512)
|
|
case 16: return (true); // nvarchar(512)
|
|
case 17: return (true); // nvarchar(max)
|
|
case 18: return (true); // text
|
|
case 19: return (true); // ntext
|
|
case 20: return (true); // binary
|
|
case 21: return (true); // varbinary(512)
|
|
case 22: return (true); // varbinary(max)
|
|
case 23: return (true); // image
|
|
case 28: return (true); // xml
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isNumeric($k)
|
|
{
|
|
switch ($k) {
|
|
case 1: return (true); // int
|
|
case 2: return (true); // tinyint
|
|
case 3: return (true); // smallint
|
|
case 4: return (true); // bigint
|
|
case 5: return (true); // bit
|
|
case 6: return (true); // float
|
|
case 7: return (true); // real
|
|
case 8: return (true); // decimal(28,4)
|
|
case 9: return (true); // numeric(32,4)
|
|
case 10: return (true); // money
|
|
case 11: return (true); // smallmoney
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isChar($k)
|
|
{
|
|
switch ($k) {
|
|
case 12: return (true); // nchar(512)
|
|
case 13: return (true); // varchar(512)
|
|
case 14: return (true); // varchar(max)
|
|
case 15: return (true); // nchar(512)
|
|
case 16: return (true); // nvarchar(512)
|
|
case 17: return (true); // nvarchar(max)
|
|
case 18: return (true); // text
|
|
case 19: return (true); // ntext
|
|
case 28: return (true); // xml
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isBinary($k)
|
|
{
|
|
switch ($k) {
|
|
case 20: return (true); // binary
|
|
case 21: return (true); // varbinary(512)
|
|
case 22: return (true); // varbinary(max)
|
|
case 23: return (true); // image
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isDateTime($k)
|
|
{
|
|
switch ($k) {
|
|
case 25: return (true); // datetime
|
|
case 26: return (true); // smalldatetime
|
|
case 27: return (true); // timestamp
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isUnicode($k)
|
|
{
|
|
switch ($k) {
|
|
case 15: return (true); // nchar(512)
|
|
case 16: return (true); // nvarchar(512)
|
|
case 17: return (true); // nvarchar(max)
|
|
case 19: return (true); // ntext
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function isUpdatable($k)
|
|
{
|
|
return ($k != 27); // timestamp
|
|
}
|
|
|
|
function isLiteral($k)
|
|
{
|
|
switch ($k) {
|
|
case 12: return (true); // nchar(512)
|
|
case 13: return (true); // varchar(512)
|
|
case 14: return (true); // varchar(max)
|
|
case 15: return (true); // nchar(512)
|
|
case 16: return (true); // nvarchar(512)
|
|
case 17: return (true); // nvarchar(max)
|
|
case 18: return (true); // text
|
|
case 19: return (true); // ntext
|
|
case 24: return (true); // uniqueidentifier
|
|
case 25: return (true); // datetime
|
|
case 26: return (true); // smalldatetime
|
|
case 28: return (true); // xml
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function getMetadata($k, $info)
|
|
{
|
|
if (strcasecmp($info, 'Name') == 0) {
|
|
return (getColName($k));
|
|
}
|
|
if (strcasecmp($info, 'Size') == 0) {
|
|
return (getColSize($k));
|
|
}
|
|
if (strcasecmp($info, 'Precision') == 0) {
|
|
return (getColPrecision($k));
|
|
}
|
|
if (strcasecmp($info, 'Scale') == 0) {
|
|
return (getColScale($k));
|
|
}
|
|
if (strcasecmp($info, 'Nullable') == 0) {
|
|
return (getColNullable($k));
|
|
}
|
|
return ("");
|
|
}
|
|
|
|
function getColName($k)
|
|
{
|
|
switch ($k) {
|
|
case 1: return ("c1_int");
|
|
case 2: return ("c2_tinyint");
|
|
case 3: return ("c3_smallint");
|
|
case 4: return ("c4_bigint");
|
|
case 5: return ("c5_bit");
|
|
case 6: return ("c6_float");
|
|
case 7: return ("c7_real");
|
|
case 8: return ("c8_decimal");
|
|
case 9: return ("c9_numeric");
|
|
case 10: return ("c10_money");
|
|
case 11: return ("c11_smallmoney");
|
|
case 12: return ("c12_char");
|
|
case 13: return ("c13_varchar");
|
|
case 14: return ("c14_varchar_max");
|
|
case 15: return ("c15_nchar");
|
|
case 16: return ("c16_nvarchar");
|
|
case 17: return ("c17_nvarchar_max");
|
|
case 18: return ("c18_text");
|
|
case 19: return ("c19_ntext");
|
|
case 20: return ("c20_binary");
|
|
case 21: return ("c21_varbinary");
|
|
case 22: return ("c22_varbinary_max");
|
|
case 23: return ("c23_image");
|
|
case 24: return ("c24_uniqueidentifier");
|
|
case 25: return ("c25_datetime");
|
|
case 26: return ("c26_smalldatetime");
|
|
case 27: return ("c27_timestamp");
|
|
case 28: return ("c28_xml");
|
|
default:
|
|
fatalError("Invalid column index $k");
|
|
break;
|
|
}
|
|
return ("");
|
|
}
|
|
|
|
function isStreamData($k)
|
|
{
|
|
switch ($k) {
|
|
case 14: return (true); // varchar(max)
|
|
case 17: return (true); // nvarchar(max)
|
|
case 18: return (true); // text
|
|
case 19: return (true); // ntext
|
|
case 20: return (true); // binary
|
|
case 21: return (true); // varbinary(512)
|
|
case 22: return (true); // varbinary(max)
|
|
case 23: return (true); // image
|
|
case 27: return (true); // timestamp
|
|
case 28: return (true); // xml
|
|
default: break;
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
function getColSize($k)
|
|
{
|
|
switch ($k) {
|
|
case 12: return ("512");
|
|
case 13: return ("512");
|
|
case 14: return ("0");
|
|
case 15: return ("512");
|
|
case 16: return ("512");
|
|
case 17: return ("0");
|
|
case 18: return ("2147483647");
|
|
case 19: return ("1073741823");
|
|
case 20: return ("512");
|
|
case 21: return ("512");
|
|
case 22: return ("0");
|
|
case 23: return ("2147483647");
|
|
case 24: return ("36");
|
|
//case 25: return ("23");
|
|
//case 26: return ("16");
|
|
case 27: return ("8");
|
|
case 28: return ("0");
|
|
default: break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
function getColPrecision($k)
|
|
{
|
|
switch ($k) {
|
|
case 1: return ("10");
|
|
case 2: return ("3");
|
|
case 3: return ("5");
|
|
case 4: return ("19");
|
|
case 5: return ("1");
|
|
case 6: return ("53");
|
|
case 7: return ("24");
|
|
case 8: return ("28");
|
|
case 9: return ("32");
|
|
case 10: return ("19");
|
|
case 11: return ("10");
|
|
case 25: return ("23");
|
|
case 26: return ("16");
|
|
default: break;
|
|
}
|
|
return ("");
|
|
}
|
|
|
|
function getColScale($k)
|
|
{
|
|
switch ($k) {
|
|
case 8: return ("4");
|
|
case 9: return ("4");
|
|
case 10: return ("4");
|
|
case 11: return ("4");
|
|
case 25: return ("3");
|
|
case 26: return ("0");
|
|
default: break;
|
|
}
|
|
return ("");
|
|
}
|
|
|
|
function getColNullable($k)
|
|
{
|
|
return (isUpdatable($k) ? "1" : "0");
|
|
}
|
|
|
|
function getSampleData($k)
|
|
{
|
|
switch ($k) {
|
|
case 1: // int
|
|
return ("123456789");
|
|
|
|
case 2: // tinyint
|
|
return ("234");
|
|
|
|
case 3: // smallint
|
|
return ("5678");
|
|
|
|
case 4: // bigint
|
|
return ("123456789987654321");
|
|
|
|
|
|
case 5: // bit
|
|
return ("1");
|
|
|
|
case 6: // float
|
|
return ("123.456");
|
|
|
|
case 7: // real
|
|
return ("789.012");
|
|
|
|
case 8: // decimal
|
|
return ("12.34");
|
|
|
|
case 9: // numeric
|
|
return ("567.89");
|
|
|
|
case 10:// money
|
|
return ("321.54");
|
|
|
|
case 11:// smallmoney
|
|
return ("67.89");
|
|
|
|
case 12:// char
|
|
case 15:// nchar
|
|
return ("The quick brown fox jumps over the lazy dog");
|
|
|
|
case 13:// varchar
|
|
case 16:// nvarchar
|
|
return ("The quick brown fox jumps over the lazy dog 9876543210");
|
|
|
|
case 14:// varchar(max)
|
|
case 17:// nvarchar(max)
|
|
return ("The quick brown fox jumps over the lazy dog 0123456789");
|
|
|
|
case 18:// text
|
|
case 19:// ntext
|
|
return ("0123456789 The quick brown fox jumps over the lazy dog");
|
|
|
|
case 20:// binary
|
|
return ("0123456789");
|
|
|
|
case 21:// varbinary
|
|
return ("01234567899876543210");
|
|
|
|
case 22:// varbinary(max)
|
|
return ("98765432100123456789");
|
|
|
|
case 23:// image
|
|
return ("01234567899876543210");
|
|
|
|
case 24:// uniqueidentifier
|
|
return ("12345678-9012-3456-7890-123456789012");
|
|
|
|
case 25:// datetime
|
|
case 26:// smalldatetime
|
|
return (date("Y-m-d"));
|
|
|
|
case 27:// timestamp
|
|
return (null);
|
|
|
|
case 28:// xml
|
|
return ("<XmlTestData><Letters1>The quick brown fox jumps over the lazy dog</Letters1><Digits1>0123456789</Digits1></XmlTestData>");
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (null);
|
|
}
|
|
}
|
|
|
|
?>
|