246 lines
7.0 KiB
PHP
246 lines
7.0 KiB
PHP
<?php
|
|
|
|
if (!defined('SQLSRV_FETCH_ASSOC')) {
|
|
define('SQLSRV_FETCH_ASSOC', 2);
|
|
}
|
|
|
|
if (!defined('SQLSRV_FETCH_NUMERIC')) {
|
|
define('SQLSRV_FETCH_NUMERIC', 1);
|
|
}
|
|
|
|
final class MysqlSqlsrvConnection
|
|
{
|
|
public PDO $pdo;
|
|
public ?array $lastError = null;
|
|
|
|
public function __construct(PDO $pdo)
|
|
{
|
|
$this->pdo = $pdo;
|
|
}
|
|
}
|
|
|
|
final class MysqlSqlsrvStatement
|
|
{
|
|
/** @var array<int, array<string, mixed>> */
|
|
public array $rows;
|
|
public int $index = 0;
|
|
public int $affectedRows = 0;
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $rows
|
|
*/
|
|
public function __construct(array $rows = [], int $affectedRows = 0)
|
|
{
|
|
$this->rows = $rows;
|
|
$this->affectedRows = $affectedRows;
|
|
}
|
|
}
|
|
|
|
function sqlsrv_connect($serverName, array $connectionOptions = [])
|
|
{
|
|
$database = getenv('DB_NAME') ?: ($connectionOptions['Database'] ?? '');
|
|
$user = getenv('DB_USER') ?: ($connectionOptions['Uid'] ?? '');
|
|
$password = getenv('DB_PASS') ?: ($connectionOptions['PWD'] ?? '');
|
|
$host = getenv('DB_HOST') ?: (string)$serverName;
|
|
$port = getenv('DB_PORT') ?: '3306';
|
|
|
|
try {
|
|
$dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $host, $port, $database);
|
|
$pdo = new PDO($dsn, $user, $password, [
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => true,
|
|
]);
|
|
|
|
return new MysqlSqlsrvConnection($pdo);
|
|
} catch (Throwable $e) {
|
|
$GLOBALS['__sqlsrv_mysql_last_error'] = [[
|
|
'SQLSTATE' => $e->getCode(),
|
|
'message' => $e->getMessage(),
|
|
]];
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function sqlsrv_query($conn, string $sql, array $params = [], array $options = [])
|
|
{
|
|
if (!$conn instanceof MysqlSqlsrvConnection) {
|
|
$GLOBALS['__sqlsrv_mysql_last_error'] = [[
|
|
'SQLSTATE' => 'HY000',
|
|
'message' => 'Invalid MySQL compatibility connection.',
|
|
]];
|
|
return false;
|
|
}
|
|
|
|
$translatedSql = sqlsrv_mysql_translate_query($sql);
|
|
|
|
try {
|
|
$stmt = $conn->pdo->prepare($translatedSql);
|
|
$stmt->execute(sqlsrv_mysql_normalize_params($translatedSql, $params));
|
|
|
|
if ($stmt->columnCount() > 0) {
|
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($rows as &$row) {
|
|
$row = sqlsrv_mysql_normalize_row($row);
|
|
}
|
|
unset($row);
|
|
|
|
return new MysqlSqlsrvStatement($rows, $stmt->rowCount());
|
|
}
|
|
|
|
return new MysqlSqlsrvStatement([], $stmt->rowCount());
|
|
} catch (Throwable $e) {
|
|
$conn->lastError = [[
|
|
'SQLSTATE' => $e->getCode(),
|
|
'message' => $e->getMessage(),
|
|
'sql' => $translatedSql,
|
|
]];
|
|
$GLOBALS['__sqlsrv_mysql_last_error'] = $conn->lastError;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function sqlsrv_fetch_array($stmt, int $fetchType = SQLSRV_FETCH_ASSOC)
|
|
{
|
|
if (!$stmt instanceof MysqlSqlsrvStatement) {
|
|
return null;
|
|
}
|
|
|
|
if (!array_key_exists($stmt->index, $stmt->rows)) {
|
|
return null;
|
|
}
|
|
|
|
$row = $stmt->rows[$stmt->index++];
|
|
|
|
if ($fetchType === SQLSRV_FETCH_NUMERIC) {
|
|
return array_values($row);
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
function sqlsrv_num_rows($stmt): int
|
|
{
|
|
if (!$stmt instanceof MysqlSqlsrvStatement) {
|
|
return 0;
|
|
}
|
|
|
|
return count($stmt->rows);
|
|
}
|
|
|
|
function sqlsrv_errors()
|
|
{
|
|
return $GLOBALS['__sqlsrv_mysql_last_error'] ?? null;
|
|
}
|
|
|
|
function sqlsrv_close($conn): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
function sqlsrv_begin_transaction($conn): bool
|
|
{
|
|
return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->beginTransaction();
|
|
}
|
|
|
|
function sqlsrv_commit($conn): bool
|
|
{
|
|
return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->commit();
|
|
}
|
|
|
|
function sqlsrv_rollback($conn): bool
|
|
{
|
|
return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->rollBack();
|
|
}
|
|
|
|
function sqlsrv_mysql_translate_query(string $sql): string
|
|
{
|
|
$query = str_replace(["\r\n", "\r"], "\n", $sql);
|
|
$query = preg_replace('/\bdbo\./i', '', $query);
|
|
$query = preg_replace('/\[([A-Za-z0-9_]+)\]/', '`$1`', $query);
|
|
|
|
$query = preg_replace('/FORMAT\(([^,]+),\s*\'yyyy\'\)\s*=\s*FORMAT\(GETDATE\(\),\s*\'yyyy\'\)/i', 'YEAR($1) = YEAR(NOW())', $query);
|
|
$query = preg_replace('/CAST\(GETDATE\(\)\s+AS\s+DATE\)/i', 'CURDATE()', $query);
|
|
$query = preg_replace('/GETDATE\(\)/i', 'NOW()', $query);
|
|
$query = preg_replace('/SYSDATETIME\(\)/i', 'NOW()', $query);
|
|
$query = preg_replace('/CONVERT\(VARCHAR,\s*([^)]+),\s*23\)/i', 'DATE($1)', $query);
|
|
|
|
$query = preg_replace(
|
|
'/DATEADD\(DAY,\s*(-?\d+),\s*\(SELECT\s+MAX\(Datum\)\s+FROM\s+kl_Kaffeeverbrauch\s+WHERE\s+Datum\s+<\s+CURDATE\(\)\)\)/i',
|
|
'DATE_ADD((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch WHERE Datum < CURDATE()), INTERVAL $1 DAY)',
|
|
$query
|
|
);
|
|
$query = preg_replace(
|
|
'/DATEADD\(DAY,\s*(-?\d+),\s*\(SELECT\s+MAX\(Datum\)\s+FROM\s+kl_Kaffeeverbrauch\s*\)\)/i',
|
|
'DATE_ADD((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch), INTERVAL $1 DAY)',
|
|
$query
|
|
);
|
|
$query = preg_replace('/DATEADD\(DAY,\s*(-?\d+),\s*NOW\(\)\)/i', 'DATE_ADD(NOW(), INTERVAL $1 DAY)', $query);
|
|
|
|
$limit = null;
|
|
$query = preg_replace_callback('/^\s*SELECT\s+TOP\s+\(?\s*(\d+)\s*\)?\s+/i', function (array $matches) use (&$limit): string {
|
|
$limit = (int)$matches[1];
|
|
return 'SELECT ';
|
|
}, $query);
|
|
|
|
if ($limit !== null && !preg_match('/\bLIMIT\s+\d+\s*$/i', trim($query))) {
|
|
$query = rtrim($query, " \t\n\r\0\x0B;") . ' LIMIT ' . $limit;
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* PDO is stricter than the SQL Server driver used by the legacy app. Some
|
|
* legacy calls pass unused parameters, and one CSV lookup passes one value for
|
|
* two equivalent placeholders. Normalize this only in the dev adapter.
|
|
*
|
|
* @param array<int, mixed> $params
|
|
* @return array<int, mixed>
|
|
*/
|
|
function sqlsrv_mysql_normalize_params(string $sql, array $params): array
|
|
{
|
|
$normalized = array_values($params);
|
|
$placeholderCount = substr_count($sql, '?');
|
|
|
|
if ($placeholderCount === 0) {
|
|
return [];
|
|
}
|
|
|
|
if (count($normalized) > $placeholderCount) {
|
|
return array_slice($normalized, 0, $placeholderCount);
|
|
}
|
|
|
|
if (count($normalized) < $placeholderCount && count($normalized) > 0) {
|
|
$last = $normalized[count($normalized) - 1];
|
|
while (count($normalized) < $placeholderCount) {
|
|
$normalized[] = $last;
|
|
}
|
|
}
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $row
|
|
* @return array<string, mixed>
|
|
*/
|
|
function sqlsrv_mysql_normalize_row(array $row): array
|
|
{
|
|
foreach ($row as $key => $value) {
|
|
if ($value === null || $value instanceof DateTimeInterface) {
|
|
continue;
|
|
}
|
|
|
|
if (is_string($value) && preg_match('/(datum|date|gueltig_bis|valid_)/i', (string)$key)) {
|
|
try {
|
|
$row[$key] = new DateTime($value);
|
|
} catch (Throwable $e) {
|
|
$row[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $row;
|
|
}
|