Pls try to replace your PdoDataSource.php content with the following code:
<?php
namespace koolreport\datasources;
use \koolreport\core\DataSource;
use \koolreport\core\Utility as Util;
use PDO;
class PdoDataSource extends DataSource
{
public static $connections;
protected $connection;
protected $query;
protected $sqlParams;
protected $queryParams;
protected $countTotal;
protected $countFilter;
protected $errorInfo;
protected function onInit()
{
$connectionString = Util::get($this->params, "connectionString", "");
$username = Util::get($this->params, "username", "");
$password = Util::get($this->params, "password", "");
$charset = Util::get($this->params, "charset");
$options = Util::get($this->params, "options");
$key = md5($connectionString . $username . $password);
if (PdoDataSource::$connections == null) {
PdoDataSource::$connections = array();
}
if (isset(PdoDataSource::$connections[$key])) {
$this->connection = PdoDataSource::$connections[$key];
} else {
$this->connection = new PDO(
$connectionString,
$username,
$password,
$options
);
PdoDataSource::$connections[$key] = $this->connection;
}
if ($charset) {
$this->connection->exec("set names '$charset'");
}
}
public function query($query, $sqlParams = null)
{
$this->originalQuery = $this->query = (string)$query;
if ($sqlParams != null) {
$this->sqlParams = $sqlParams;
}
return $this;
}
public function escapeStr($value)
{
return $this->connection->quote($value);
}
public function queryProcessing($queryParams)
{
$this->queryParams = $queryParams;
$driver = strtolower($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME));
switch ($driver) {
case 'mysql':
list($this->query, $this->totalQuery, $this->filterQuery, $this->aggregates)
= MySQLDataSource::processQuery($this->originalQuery, $queryParams);
break;
case 'oci':
list($this->query, $this->totalQuery, $this->filterQuery)
= OracleDataSource::processQuery($this->originalQuery, $queryParams);
break;
case 'pgsql':
list($this->query, $this->totalQuery, $this->filterQuery)
= PostgreSQLDataSource::processQuery($this->originalQuery, $queryParams);
break;
case 'sqlsrv':
list($this->query, $this->totalQuery, $this->filterQuery)
= SQLSRVDataSource::processQuery($this->originalQuery, $queryParams);
break;
default:
break;
}
$this->countTotal = Util::get($queryParams, 'countTotal', false);
$this->countFilter = Util::get($queryParams, 'countFilter', false);
return $this;
}
public function params($sqlParams)
{
$this->sqlParams = $sqlParams;
return $this;
}
protected function prepareAndBind($query, $params)
{
if (empty($params)) {
$params = [];
}
$paNames = array_keys($params);
usort(
$paNames,
function ($k1, $k2) {
return strlen($k2) - strlen($k1);
}
);
$query = $query;
foreach ($paNames as $paName) {
$paValue = $params[$paName];
if (gettype($paValue) === "array") {
$numValues = strlen((string)count($paValue));
$paramList = [];
foreach ($paValue as $i => $value) {
$order = $i + 1;
$order = str_pad($order, $numValues, "0", STR_PAD_LEFT);
$paArrElName = $paName . "_arr_$order";
$paramList[] = $paArrElName;
$params[$paArrElName] = $value;
}
$query = str_replace($paName, implode(",", $paramList), $query);
}
}
$paNames = array_keys($params);
usort(
$paNames,
function ($k1, $k2) {
return strlen($k2) - strlen($k1);
}
);
$newParams = [];
$hashedPaNames = [];
foreach ($paNames as $paName) {
$count = 1;
$pos = -1;
while (true) {
$pos = strpos($query, $paName, $pos + 1);
if ($pos === false) {
break;
} else {
$newPaName = $count > 1 ? $paName . "_" . $count : $paName;
$newParams[$newPaName] = $params[$paName];
$hashedPaName = md5($newPaName);
$hashedPaNames[$newPaName] = $hashedPaName;
$query = substr_replace($query, $hashedPaName, $pos, strlen($paName));
}
$count++;
}
}
foreach ($newParams as $newPaName => $value) {
$hashedPaName = $hashedPaNames[$newPaName];
$query = str_replace($hashedPaName, $newPaName, $query);
}
$stm = $this->connection->prepare($query);
foreach ($newParams as $paName => $paValue) {
$type = gettype($paValue);
$paramType = $this->typeToPDOParamType($type);
$stm->bindValue($paName, $paValue, $paramType);
}
return $stm;
}
protected function typeToPDOParamType($type)
{
switch ($type) {
case "boolean":
return PDO::PARAM_BOOL;
case "integer":
return PDO::PARAM_INT;
case "NULL":
return PDO::PARAM_NULL;
case "resource":
return PDO::PARAM_LOB;
case "double":
case "string":
default:
return PDO::PARAM_STR;
}
}
protected function guessType($native_type)
{
$map = array(
"character" => "string",
"char" => "string",
"string" => "string",
"str" => "string",
"text" => "string",
"blob" => "string",
"binary" => "string",
"enum" => "string",
"set" => "string",
"int" => "number",
"double" => "number",
"float" => "number",
"long" => "number",
"numeric" => "number",
"decimal" => "number",
"real" => "number",
"tinyint" => "number",
"bit" => "number",
"boolean" => "number",
"datetime" => "datetime",
"date" => "date",
"time" => "time",
"year" => "datetime",
);
$native_type = strtolower($native_type);
foreach ($map as $key => $value) {
if (strpos($native_type, $key) !== false) {
return $value;
}
}
return "unknown";
}
protected function guessTypeFromValue($value)
{
$map = array(
"float" => "number",
"double" => "number",
"int" => "number",
"integer" => "number",
"bool" => "number",
"numeric" => "number",
"string" => "string",
);
$type = strtolower(gettype($value));
foreach ($map as $key => $value) {
if (strpos($type, $key) !== false) {
return $value;
}
}
return "unknown";
}
protected function buildMetaData()
{
$metaData = array("columns" => array());
if (empty($this->sqlParams)) $this->sqlParams = [];
$searchParams = Util::get($this->queryParams, 'searchParams', []);
if (empty($searchParams)) $searchParams = [];
if ($this->countTotal) {
$stm = $this->prepareAndBind($this->totalQuery, $this->sqlParams);
$stm->execute();
$error = $stm->errorInfo();
if ($error[2] != null) {
throw new \Exception(
"Query Error >> " . json_encode($error) . " >> $this->totalQuery"
. " || Sql params = " . json_encode($this->sqlParams)
);
return;
}
$row = $stm->fetch();
$stm->closeCursor();
$result = $row[0];
$metaData['totalRecords'] = $result;
}
if ($this->countFilter) {
$stm = $this->prepareAndBind($this->filterQuery, array_merge($this->sqlParams, $searchParams));
$stm->execute();
$error = $stm->errorInfo();
if ($error[2] != null) {
throw new \Exception(
"Query Error >> " . json_encode($error) . " >> $this->filterQuery"
. " || Sql params = " . json_encode($this->sqlParams)
. " || Search params = " . json_encode($searchParams)
);
}
$row = $stm->fetch();
$stm->closeCursor();
$result = $row[0];
$metaData['filterRecords'] = $result;
}
if (!empty($this->aggregates)) {
foreach ($this->aggregates as $aggregate) {
$operator = $aggregate["operator"];
$field = $aggregate["field"];
$aggQuery = $aggregate["aggQuery"];
$stm = $this->prepareAndBind($aggQuery, array_merge($this->sqlParams, $searchParams));
$stm->execute();
$error = $stm->errorInfo();
if ($error[2] != null) {
throw new \Exception(
"Query Error >> " . json_encode($error) . " >> $aggQuery"
. " || Sql params = " . json_encode($this->sqlParams)
. " || Search params = " . json_encode($searchParams)
);
}
$row = $stm->fetch();
$stm->closeCursor();
$result = $row[0];
Util::set($metaData, ['aggregates', $operator, $field], $result);
}
}
$driver = strtolower($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME));
$metaSupportDrivers = array('dblib', 'mysql', 'pgsql', 'sqlite');
$metaSupport = false;
foreach ($metaSupportDrivers as $supportDriver) {
if (strpos($driver, $supportDriver) !== false) {
$metaSupport = true;
}
}
$query = $this->query;
$this->stm = $this->prepareAndBind($query, array_merge($this->sqlParams, $searchParams));
$this->stm->execute();
$error = $this->stm->errorInfo();
if ($error[0] != '00000') {
throw new \Exception(
"Query Error >> " . json_encode($error) . " >> $query"
. " || Sql params = " . json_encode($this->sqlParams)
. " || Search params = " . json_encode($searchParams)
);
}
$this->firstRow = null;
if (!$metaSupport) {
$this->firstRow = $this->stm->fetch(PDO::FETCH_ASSOC);
$cNames = empty($row) ? array() : array_keys($row);
$numcols = count($cNames);
} else {
$numcols = $this->stm->columnCount();
}
for ($i = 0; $i < $numcols; $i++) {
if (!$metaSupport) {
$cName = $cNames[$i];
$cType = $this->guessTypeFromValue($row[$cName]);
} else {
$info = $this->stm->getColumnMeta($i);
$cName = $info["name"];
$cType = $this->guessType(Util::get($info, "native_type", "unknown"));
}
$metaData["columns"][$cName] = array(
"type" => $cType,
);
switch ($cType) {
case "datetime":
$metaData["columns"][$cName]["format"] = "Y-m-d H:i:s";
break;
case "date":
$metaData["columns"][$cName]["format"] = "Y-m-d";
break;
case "time":
$metaData["columns"][$cName]["format"] = "H:i:s";
break;
}
}
$this->builtMetaData = $metaData;
}
public function start()
{
if (isset($this->params[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY])) {
$mysqlBuffer = $this->params[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY];
$this->connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $mysqlBuffer);
}
$this->buildMetaData();
$this->sendMeta($this->builtMetaData, $this);
$this->startInput(null);
if (!isset($this->firstRow)) {
$row = $this->stm->fetch(PDO::FETCH_ASSOC);
} else {
$row = $this->firstRow;
}
while ($row) {
$this->next($row, $this);
$row = $this->stm->fetch(PDO::FETCH_ASSOC);
}
$this->endInput(null);
$this->stm->closeCursor();
$this->endOfStm = true;
}
public function startMetaOnly()
{
if (!isset($this->builtMetaData)) {
$this->buildMetaData();
$this->sendMeta($this->builtMetaData, $this);
}
}
public function startGenerator($genName)
{
$this->startInput(null);
$report = $this->getReport();
$report->saveDataGenRow = $genName;
if (isset($this->endOfStm) && $this->endOfStm === true) {
$this->stm->execute();
$this->firstRow = $this->stm->fetch(PDO::FETCH_ASSOC);
$this->endOfStm = false;
}
if (!isset($this->firstRow)) {
$row = $this->stm->fetch(PDO::FETCH_ASSOC);
} else {
$row = $this->firstRow;
}
if (!isset($report->dataGenRow)) $report->dataGenRow = [];
while ($row) {
$this->next($row, $this);
foreach ($report->dataGenRow as $outGenName => $rows) {
if ($outGenName !== $genName) {
$report->dataGenRow[$outGenName] = [];
continue;
}
if (!empty($rows)) {
foreach ($rows as $row) yield $genName => $row;
$report->dataGenRow[$genName] = [];
}
}
$row = $this->stm->fetch(PDO::FETCH_ASSOC);
}
$report->saveDataGenRow = null;
$this->endInput(null);
$this->endOfStm = true;
$this->stm->closeCursor();
}
public function fetchFields($query)
{
$columns = [];
$stm = $this->prepareAndBind($query, $this->sqlParams);
$stm->execute();
$error = $stm->errorInfo();
if ($error[0] != '00000') {
throw new \Exception(
"Query Error >> " . json_encode($error) . " >> $query"
);
}
$driver = strtolower($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME));
$metaSupportDrivers = array('dblib', 'mysql', 'pgsql', 'sqlite');
$metaSupport = false;
foreach ($metaSupportDrivers as $supportDriver) {
if (strpos($driver, $supportDriver) !== false) {
$metaSupport = true;
}
}
if (!$metaSupport) {
$row = $stm->fetch(PDO::FETCH_ASSOC);
$cNames = empty($row) ? array() : array_keys($row);
$numcols = count($cNames);
} else {
$numcols = $stm->columnCount();
}
for ($i = 0; $i < $numcols; $i++) {
if (!$metaSupport) {
$cName = $cNames[$i];
$cType = $this->guessTypeFromValue($row[$cName]);
} else {
$info = $stm->getColumnMeta($i);
$cName = $info["name"];
$cType = $this->guessType(Util::get($info, "native_type", "unknown"));
}
$columns[$cName] = array(
"type" => $cType,
);
switch ($cType) {
case "datetime":
$columns[$cName]["format"] = "Y-m-d H:i:s";
break;
case "date":
$columns[$cName]["format"] = "Y-m-d";
break;
case "time":
$columns[$cName]["format"] = "H:i:s";
break;
}
}
$stm->closeCursor();
return $columns;
}
public function fetchData($query, $queryParams = null)
{
if (
isset($queryParams) &&
(isset($queryParams['countTotal']) || isset($queryParams['countFilter']))
) {
$driver = strtolower($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME));
switch ($driver) {
case 'mysql':
list($query, $totalQuery, $filterQuery)
= MySQLDataSource::processQuery($query, $queryParams);
break;
case 'oci':
list($query, $totalQuery, $filterQuery)
= OracleDataSource::processQuery($query, $queryParams);
break;
case 'pgsql':
list($query, $totalQuery, $filterQuery)
= PostgreSQLDataSource::processQuery($query, $queryParams);
break;
case 'sqlsrv':
list($query, $totalQuery, $filterQuery)
= SQLSRVDataSource::processQuery($query, $queryParams);
break;
default:
break;
}
$queries = [
'data' => $query,
'total' => $totalQuery,
'filter' => $filterQuery
];
} else {
$queries = [
'data' => $query
];
}
$result = [];
foreach ($queries as $key => $query) {
$stm = $this->prepareAndBind($query, $this->sqlParams);
$stm->execute();
$error = $stm->errorInfo();
if ($error[0] != '00000') {
throw new \Exception(
"Query Error >> " . json_encode($error) . " >> $query"
. " || Sql params = " . json_encode($this->sqlParams)
);
}
$rows = [];
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
$rows[] = $row;
}
$result[$key] = $rows;
}
$stm->closeCursor();
return $result;
}
public function errorInfo()
{
return $this->errorInfo;
}
public function execute($sql, $params = null)
{
if (is_array($params)) {
$stm = $this->prepareAndBind($sql, $params);
$success = $stm->execute();
if ($success === false) {
$this->errorInfo = $stm->errorInfo();
} else {
$this->errorInfo = null;
}
} else {
$success = $this->connection->exec($sql);
if ($success === false) {
$this->errorInfo = $this->connection->errorInfo();
} else {
$this->errorInfo = null;
}
}
$stm->closeCursor();
return $success !== false;
}
}