Skip to content

Commit

Permalink
Merge pull request #22 from byjg/5.0
Browse files Browse the repository at this point in the history
Add SqlStatement object
  • Loading branch information
byjg authored Dec 6, 2024
2 parents 36f6763 + d0f6645 commit 1473ae6
Show file tree
Hide file tree
Showing 19 changed files with 500 additions and 311 deletions.
18 changes: 16 additions & 2 deletions docs/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,26 @@
You can easily cache your results to speed up the results of long queries;
You need to add to your project an implementation of PSR-16. We suggested you add "byjg/cache".

Also, you need to use the `SqlStatement` class to prepare the query and cache the results.

```php
<?php
$dbDriver = \ByJG\AnyDataset\Db\Factory::getDbInstance('mysql://username:password@host/database');
$dbDriver = Factory::getDbInstance('mysql://username:password@host/database');
$cache = new \ByJG\Cache\Psr16\ArrayCacheEngine()

// Define the SqlStatement object
$sql = new SqlStatement("select * from table where field = :param");
$sql->withCache($cache, 'my_cache_key', 60);

// Query using the PSR16 cache interface.
// If not exists, will cache. If exists will get from cache.
$iterator = $dbDriver>getIterator('select * from table where field = :param', ['param' => 'value'], $cache, 60);
$iterator = $sql->getIterator($dbDriver, ['param' => 'value']);
```

**NOTES**

- It will be saved one cache entry for each different parameters.
e.g. `['param' => 'value']` and `['param' => 'value2']` will have one entry for each result.

- If you use the same key for different sql statements it will not differentiate one from another and
you can get unexpected results
19 changes: 19 additions & 0 deletions docs/sqlstatement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# SQL Statement

The SQL Statement is a class to abstract the SQL query from the database.

```php
<?php

$dbDriver = Factory::getDbInstance("mysql://user:password@server/schema");
$sql = new SqlStatement("select * from table where field = :param");

$iterator = $sql->getIterator($dbDriver, ['param' => 'value']);
```

The advantage of using the `SqlStatement` is that you can reuse the same SQL statement with different parameters.
It saves time preparing the cache.

Also, you can cache the query (see [Cache results](cache.md)).


13 changes: 8 additions & 5 deletions src/DbDriverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,35 @@
namespace ByJG\AnyDataset\Db;

use ByJG\AnyDataset\Core\GenericIterator;
use ByJG\AnyDataset\Db\Interfaces\DbCacheInterface;
use ByJG\AnyDataset\Db\Interfaces\DbTransactionInterface;
use ByJG\Util\Uri;
use DateInterval;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;

interface DbDriverInterface extends DbTransactionInterface, DbCacheInterface
interface DbDriverInterface extends DbTransactionInterface
{

public static function schema();

public function prepareStatement(string $sql, ?array $params = null, ?array &$cacheInfo = []): mixed;

public function executeCursor(mixed $statement): void;

/**
* @param string $sql
* @param array|null $params
* @param CacheInterface|null $cache
* @param int|DateInterval $ttl
* @return GenericIterator
*/
public function getIterator(string $sql, ?array $params = null, ?CacheInterface $cache = null, DateInterval|int $ttl = 60): GenericIterator;
public function getIterator(mixed $sql, ?array $params = null, ?CacheInterface $cache = null, DateInterval|int $ttl = 60): GenericIterator;

public function getScalar(string $sql, ?array $array = null): mixed;
public function getScalar(mixed $sql, ?array $array = null): mixed;

public function getAllFields(string $tablename): array;

public function execute(string $sql, ?array $array = null): bool;
public function execute(mixed $sql, ?array $array = null): bool;

public function executeAndGetId(string $sql, ?array $array = null): mixed;

Expand Down
2 changes: 2 additions & 0 deletions src/DbFunctionsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,6 @@ public function hasForUpdate(): bool;
public function getTableMetadata(DbDriverInterface $dbdataset, string $tableName): array;

public function getIsolationLevelCommand(?IsolationLevelEnum $isolationLevel = null): string;

public function getJoinTablesUpdate(array $tables): array;
}
105 changes: 68 additions & 37 deletions src/DbOci8Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
use ByJG\Util\Uri;
use DateInterval;
use Exception;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Psr\SimpleCache\CacheInterface;

class DbOci8Driver implements DbDriverInterface
{
use TransactionTrait;
use DbCacheTrait;
use TransactionTrait;

private LoggerInterface $logger;

Expand Down Expand Up @@ -93,19 +94,19 @@ public function __destruct()

/**
* @param string $sql
* @param array|null $array
* @param array|null $params
* @return resource
* @throws DatabaseException
* @throws DbDriverNotConnected
*/
protected function getOci8Cursor(string $sql, array $array = null)
public function prepareStatement(string $sql, array $params = null, ?array &$cacheInfo = []): mixed
{
if (is_null($this->conn)) {
throw new DbDriverNotConnected('Instance not connected');
}
list($query, $array) = SqlBind::parseSQL($this->connectionUri, $sql, $array);
list($query, $params) = SqlBind::parseSQL($this->connectionUri, $sql, $params);

$this->logger->debug("SQL: $query, Params: " . json_encode($array));
$this->logger->debug("SQL: $query, Params: " . json_encode($params));

// Prepare the statement
$query = rtrim($query, ' ;');
Expand All @@ -116,63 +117,82 @@ protected function getOci8Cursor(string $sql, array $array = null)
}

// Bind the parameters
if (is_array($array)) {
foreach ($array as $key => $value) {
oci_bind_by_name($stid, ":$key", $array[$key], -1);
if (is_array($params)) {
foreach ($params as $key => $value) {
oci_bind_by_name($stid, ":$key", $params[$key], -1);
}
}

return $stid;
}

public function executeCursor(mixed $statement): void
{
// Perform the logic of the query
$result = oci_execute($stid, $this->ociAutoCommit);
$result = oci_execute($statement, $this->ociAutoCommit);

// Check if is OK;
if (!$result) {
$error = oci_error($stid);
$error = oci_error($statement);
throw new DatabaseException($error['message']);
}

return $stid;
}

/**
* @param string $sql
* @param mixed $sql
* @param array|null $params
* @param CacheInterface|null $cache
* @param int|DateInterval $ttl
* @return GenericIterator
* @throws DatabaseException
* @throws DbDriverNotConnected
*/
public function getIterator(string $sql, ?array $params = null, ?CacheInterface $cache = null, DateInterval|int $ttl = 60): GenericIterator
public function getIterator(mixed $sql, ?array $params = null, ?CacheInterface $cache = null, DateInterval|int $ttl = 60): GenericIterator
{
return $this->getIteratorUsingCache($sql, $params, $cache, $ttl, function ($sql, $params) {
$cur = $this->getOci8Cursor($sql, $params);
return new Oci8Iterator($cur);
});
if (is_resource($sql)) {
return new Oci8Iterator($sql);
}

if (is_string($sql)) {
$sql = new SqlStatement($sql);
if (!empty($cache)) {
$sql->withCache($cache, $this->getQueryKey($cache, $sql->getSql(), $params), $ttl);
}
} elseif (!($sql instanceof SqlStatement)) {
throw new InvalidArgumentException("The SQL must be a cursor, string or a SqlStatement object");
}

return $sql->getIterator($this, $params);
}

/**
* @param string $sql
* @param mixed $sql
* @param array|null $array
* @return mixed
* @throws DatabaseException
* @throws DbDriverNotConnected
*/
public function getScalar(string $sql, ?array $array = null): mixed
public function getScalar(mixed $sql, ?array $array = null): mixed
{
$cur = $this->getOci8Cursor($sql, $array);
if (is_resource($sql)) {
/** @psalm-suppress UndefinedConstant */
$row = oci_fetch_array($sql, OCI_RETURN_NULLS);
if ($row) {
$scalar = $row[0];
} else {
$scalar = false;
}

/** @psalm-suppress UndefinedConstant */
$row = oci_fetch_array($cur, OCI_RETURN_NULLS);
if ($row) {
$scalar = $row[0];
} else {
$scalar = false;
oci_free_cursor($sql);

return $scalar;
}

oci_free_cursor($cur);
if (is_string($sql)) {
$sql = new SqlStatement($sql);
} elseif (!($sql instanceof SqlStatement)) {
throw new InvalidArgumentException("The SQL must be a cursor, string or a SqlStatement object");
}

return $scalar;
return $sql->getScalar($this, $array);
}

/**
Expand All @@ -183,7 +203,8 @@ public function getScalar(string $sql, ?array $array = null): mixed
*/
public function getAllFields(string $tablename): array
{
$cur = $this->getOci8Cursor(SqlHelper::createSafeSQL("select * from :table", array(':table' => $tablename)));
$cur = $this->prepareStatement(SqlHelper::createSafeSQL("select * from :table", array(':table' => $tablename)));
$this->executeCursor($cur);

$ncols = oci_num_fields($cur);

Expand Down Expand Up @@ -237,16 +258,27 @@ protected function transactionHandler(TransactionStageEnum $action, string $isoL
}

/**
* @param string $sql
* @param mixed $sql
* @param array|null $array
* @return bool
* @throws DatabaseException
*/
public function execute(string $sql, ?array $array = null): bool
public function execute(mixed $sql, ?array $array = null): bool
{
$cur = $this->getOci8Cursor($sql, $array);
oci_free_cursor($cur);
if (is_resource($sql)) {
oci_free_cursor($sql);
return true;
}

if (is_string($sql)) {
$sql = new SqlStatement($sql);
} elseif (!($sql instanceof SqlStatement)) {
throw new InvalidArgumentException("The SQL must be a cursor, string or a SqlStatement object");
}

$sql->execute($this, $array);
return true;

}

/**
Expand Down Expand Up @@ -363,7 +395,6 @@ public function reconnect(bool $force = false): bool

public function disconnect(): void
{
$this->clearCache();
$this->conn = null;
}

Expand Down
Loading

0 comments on commit 1473ae6

Please sign in to comment.