From 303f54d88b02e5379c64caabdcbd7d22e8841976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20=C5=9EAFAK?= Date: Fri, 15 Dec 2023 05:36:20 +0300 Subject: [PATCH] Driver Escape Indentity Char --- src/Drivers/BaseDriver.php | 43 +++ src/Drivers/DriverInterface.php | 31 ++ src/Drivers/MySQL.php | 24 ++ src/Drivers/PgSQL.php | 35 +++ src/Drivers/SQLite.php | 24 ++ src/Parameters.php | 5 +- src/QueryBuilder.php | 95 ++++-- src/QueryBuilderFactory.php | 6 +- src/QueryBuilderFactoryInterface.php | 5 +- tests/AbstractQueryBuilderDriverUnit.php | 32 ++ tests/DeleteQueryDriverUnitTest.php | 33 ++ tests/DeleteQueryUnitTest.php | 3 +- tests/InsertQueryDriverUnitTest.php | 61 ++++ tests/SelectQueryDriverUnitTest.php | 375 +++++++++++++++++++++++ tests/SelectQueryUnitTest.php | 1 + tests/UpdateQueryDriverUnitTest.php | 60 ++++ 16 files changed, 801 insertions(+), 32 deletions(-) create mode 100644 src/Drivers/BaseDriver.php create mode 100644 src/Drivers/DriverInterface.php create mode 100644 src/Drivers/MySQL.php create mode 100644 src/Drivers/PgSQL.php create mode 100644 src/Drivers/SQLite.php create mode 100644 tests/AbstractQueryBuilderDriverUnit.php create mode 100644 tests/DeleteQueryDriverUnitTest.php create mode 100644 tests/InsertQueryDriverUnitTest.php create mode 100644 tests/SelectQueryDriverUnitTest.php create mode 100644 tests/UpdateQueryDriverUnitTest.php diff --git a/src/Drivers/BaseDriver.php b/src/Drivers/BaseDriver.php new file mode 100644 index 0000000..728bcb4 --- /dev/null +++ b/src/Drivers/BaseDriver.php @@ -0,0 +1,43 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +namespace InitORM\QueryBuilder\Drivers; + +class BaseDriver implements DriverInterface +{ + + protected string $name; + + protected string $escapeChar = ''; + + /** + * @inheritDoc + */ + public function escapeIdentify(string &$string): string + { + if (!empty($this->escapeChar)) { + $string = preg_replace('/\b(?escapeChar . '$0' . $this->escapeChar, str_replace($this->escapeChar, $this->escapeChar . $this->escapeChar, trim($string, $this->escapeChar))); + } + + return $string; + } + + /** + * @inheritDoc + */ + public function getDriver(): ?string + { + return $this->name ?? null; + } + +} diff --git a/src/Drivers/DriverInterface.php b/src/Drivers/DriverInterface.php new file mode 100644 index 0000000..3aa584d --- /dev/null +++ b/src/Drivers/DriverInterface.php @@ -0,0 +1,31 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace InitORM\QueryBuilder\Drivers; + +interface DriverInterface +{ + + /** + * @param string $string + * @return string + */ + public function escapeIdentify(string &$string): string; + + /** + * @return string|null + */ + public function getDriver(): ?string; + +} diff --git a/src/Drivers/MySQL.php b/src/Drivers/MySQL.php new file mode 100644 index 0000000..0a431df --- /dev/null +++ b/src/Drivers/MySQL.php @@ -0,0 +1,24 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace InitORM\QueryBuilder\Drivers; + +class MySQL extends BaseDriver +{ + + protected string $name = 'mysql'; + + protected string $escapeChar = '`'; + +} diff --git a/src/Drivers/PgSQL.php b/src/Drivers/PgSQL.php new file mode 100644 index 0000000..ef1b882 --- /dev/null +++ b/src/Drivers/PgSQL.php @@ -0,0 +1,35 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace InitORM\QueryBuilder\Drivers; + +class PgSQL extends BaseDriver +{ + + protected string $escapeChar = '"'; + + protected string $name = 'pgsql'; + + /** + * @inheritDoc + */ + public function escapeIdentify(string &$string): string + { + parent::escapeIdentify($string); + //$string = preg_replace('/(?escapeChar . '$1' . $this->escapeChar, $string); + + return $string; + } + +} diff --git a/src/Drivers/SQLite.php b/src/Drivers/SQLite.php new file mode 100644 index 0000000..f8b41a0 --- /dev/null +++ b/src/Drivers/SQLite.php @@ -0,0 +1,24 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace InitORM\QueryBuilder\Drivers; + +class SQLite extends BaseDriver +{ + + protected string $name = 'sqlite'; + + protected string $escapeChar = '`'; + +} diff --git a/src/Parameters.php b/src/Parameters.php index 2a66fe4..9c5cb08 100644 --- a/src/Parameters.php +++ b/src/Parameters.php @@ -7,7 +7,7 @@ * @author Muhammet ŞAFAK * @copyright Copyright © 2023 Muhammet ŞAFAK * @license ./LICENSE MIT - * @version 1.0 + * @version 1.0.1 * @link https://www.muhammetsafak.com.tr */ @@ -31,7 +31,7 @@ public function __construct() */ public function set(string $key, mixed $value): self { - $this->parameters[':' . ltrim(str_replace('.', '', $key), ':')] = $value; + $this->parameters[':' . preg_replace("/[^A-Za-z0-9_]/", "", $key)] = $value; return $this; } @@ -47,6 +47,7 @@ public function add(RawQuery|string $key, mixed $value): string if ($key instanceof RawQuery) { $key = md5((string)$key); } + $key = preg_replace("/[^A-Za-z0-9_]/", "", $key); $originKey = ltrim(str_replace('.', '', $key), ':'); $i = 0; do { diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index b91f6ae..aaaf4ea 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -7,7 +7,7 @@ * @author Muhammet ŞAFAK * @copyright Copyright © 2023 Muhammet ŞAFAK * @license ./LICENSE MIT - * @version 1.0 + * @version 1.0.1 * @link https://www.muhammetsafak.com.tr */ @@ -15,6 +15,11 @@ namespace InitORM\QueryBuilder; use Closure; +use InitORM\QueryBuilder\Drivers\BaseDriver; +use InitORM\QueryBuilder\Drivers\DriverInterface; +use InitORM\QueryBuilder\Drivers\MySQL; +use InitORM\QueryBuilder\Drivers\PgSQL; +use InitORM\QueryBuilder\Drivers\SQLite; use InitORM\QueryBuilder\Exceptions\{QueryBuilderInvalidArgumentException, QueryBuilderException}; class QueryBuilder implements QueryBuilderInterface @@ -47,10 +52,19 @@ class QueryBuilder implements QueryBuilderInterface protected ParameterInterface $parameters; - public function __construct() + protected DriverInterface $driver; + + public function __construct(?string $driver = null) { $this->structure = self::STRUCTURE; $this->parameters = new Parameters(); + + $this->driver = match ($driver) { + 'mysql' => new MySQL(), + 'pgsql', 'postgres', 'postgresql' => new PgSQL(), + 'sqlite' => new SQLite(), + default => new BaseDriver(), + }; } /** @@ -78,7 +92,7 @@ public function __toString(): string */ public function newBuilder(): self { - return new self(); + return new self($this->driver->getDriver()); } /** @@ -172,6 +186,7 @@ public function setParameters(array $parameters = []): self public function select(...$columns): self { foreach ($columns as $column) { + is_string($column) && $this->driver->escapeIdentify($column); $column = (string)$column; $this->structure['select'][] = $column; } @@ -194,8 +209,9 @@ public function clearSelect(): self */ public function selectCount(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'COUNT(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -205,8 +221,9 @@ public function selectCount(RawQuery|string $column, ?string $alias = null): sel */ public function selectCountDistinct(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'COUNT(DISTINCT ' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -215,8 +232,9 @@ public function selectCountDistinct(RawQuery|string $column, ?string $alias = nu */ public function selectMax(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'MAX(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -226,8 +244,9 @@ public function selectMax(RawQuery|string $column, ?string $alias = null): self */ public function selectMin(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'MIN(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -237,8 +256,9 @@ public function selectMin(RawQuery|string $column, ?string $alias = null): self */ public function selectAvg(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'AVG(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -248,7 +268,8 @@ public function selectAvg(RawQuery|string $column, ?string $alias = null): self */ public function selectAs(RawQuery|string $column, string $alias): self { - $this->structure['select'][] = $column . ' AS ' . $alias; + is_string($column) && $this->driver->escapeIdentify($column); + $this->structure['select'][] = $column . ' AS ' . $this->driver->escapeIdentify($alias); return $this; } @@ -258,8 +279,9 @@ public function selectAs(RawQuery|string $column, string $alias): self */ public function selectUpper(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'UPPER(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -269,8 +291,9 @@ public function selectUpper(RawQuery|string $column, ?string $alias = null): sel */ public function selectLower(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'LOWER(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -280,8 +303,9 @@ public function selectLower(RawQuery|string $column, ?string $alias = null): sel */ public function selectLength(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'LENGTH(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -291,8 +315,9 @@ public function selectLength(RawQuery|string $column, ?string $alias = null): se */ public function selectMid(RawQuery|string $column, int $offset, int $length, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'MID(' . $column . ', ' . $offset . ', ' . $length . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -302,8 +327,9 @@ public function selectMid(RawQuery|string $column, int $offset, int $length, ?st */ public function selectLeft(RawQuery|string $column, int $length, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'LEFT(' . $column . ', ' . $length . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -313,8 +339,9 @@ public function selectLeft(RawQuery|string $column, int $length, ?string $alias */ public function selectRight(RawQuery|string $column, int $length, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'RIGHT(' . $column . ', ' . $length . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -324,8 +351,9 @@ public function selectRight(RawQuery|string $column, int $length, ?string $alias */ public function selectDistinct(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'DISTINCT(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -335,8 +363,10 @@ public function selectDistinct(RawQuery|string $column, ?string $alias = null): */ public function selectCoalesce(RawQuery|string $column, $default = '0', ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); + is_string($default) && !is_numeric($default) && $this->driver->escapeIdentify($default); $this->structure['select'][] = 'COALESCE(' . $column . ', ' . $default . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -346,8 +376,9 @@ public function selectCoalesce(RawQuery|string $column, $default = '0', ?string */ public function selectSum(RawQuery|string $column, ?string $alias = null): self { + is_string($column) && $this->driver->escapeIdentify($column); $this->structure['select'][] = 'SUM(' . $column . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -358,10 +389,11 @@ public function selectSum(RawQuery|string $column, ?string $alias = null): self public function selectConcat(array $columns, ?string $alias = null): self { foreach ($columns as &$column) { + is_string($column) && $this->driver->escapeIdentify($column); $column = (string)$column; } $this->structure['select'][] = 'CONCAT(' . implode(', ', $columns) . ')' - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this; } @@ -381,7 +413,8 @@ public function from(RawQuery|string $table, ?string $alias = null): self */ public function addFrom(RawQuery|string $table, ?string $alias = null): self { - $table = $table . ($alias !== null ? ' AS ' . $alias : ''); + is_string($table) && $this->driver->escapeIdentify($table); + $table = $table . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); if (!in_array($table, $this->structure['table'], true)) { $this->structure['table'][] = $table; } @@ -394,6 +427,7 @@ public function addFrom(RawQuery|string $table, ?string $alias = null): self */ public function table(RawQuery|string $table): self { + is_string($table) && $this->driver->escapeIdentify($table); $this->structure['table'] = [(string)$table]; return $this; @@ -410,6 +444,7 @@ public function groupBy(string|RawQuery|array ...$columns): self continue; } + is_string($column) && $this->driver->escapeIdentify($column); $column = (string)$column; if (!in_array($column, $this->structure['group_by'])) { $this->structure['group_by'][] = $column; @@ -424,6 +459,7 @@ public function groupBy(string|RawQuery|array ...$columns): self */ public function join(RawQuery|string $table, RawQuery|Closure|string $onStmt = null, string $type = 'INNER'): self { + is_string($table) && $type !== 'SELF' && $this->driver->escapeIdentify($table); $table = (string)$table; if ($onStmt instanceof Closure) { @@ -441,6 +477,8 @@ public function join(RawQuery|string $table, RawQuery|Closure|string $onStmt = n } $onStmt = $builder->__generateOnQuery(); } + } else if (is_string($onStmt)) { + $this->driver->escapeIdentify($onStmt); } $type = trim(strtoupper($type)); @@ -525,6 +563,8 @@ public function orderBy(RawQuery|string $column, string $soft = 'ASC'): self if (!in_array($soft, ['ASC', 'DESC'], true)) { throw new QueryBuilderInvalidArgumentException('It can only sort as ASC or DESC.'); } + is_string($column) && $this->driver->escapeIdentify($column); + $orderBy = trim((string)$column) . ' ' . $soft; !in_array($orderBy, $this->structure['order_by'], true) && $this->structure['order_by'][] = $orderBy; @@ -563,6 +603,10 @@ public function on(RawQuery|string $column, mixed $operator = '=', mixed $value { $this->whereOrHavingPrepare($operator, $value, $logical); + if (is_string($value) && str_contains($value, '.')) { + $value = $this->raw($this->driver->escapeIdentify($value)); + } + $this->structure['on'][$logical][] = $this->whereOrHavingStatementPrepare($column, $operator, $value); return $this; @@ -585,13 +629,16 @@ public function addSet(RawQuery|array|string $column, mixed $value = null, bool $set = []; foreach ($column as $name => $value) { $name = (string)$name; - $set[$name] = $this->isSQLParameterOrFunction($value) ? $value : $this->parameters->add($name, $value); + $value = $this->isSQLParameterOrFunction($value) ? $value : $this->parameters->add($name, $value); + $this->driver->escapeIdentify($name); + $set[$name] = $value; } $this->structure['set'][] = $set; return $this; } + is_string($column) && $this->driver->escapeIdentify($column); $column = (string)$column; $value = $this->isSQLParameterOrFunction($value) ? $value : $this->parameters->add($column, $value); @@ -1059,7 +1106,7 @@ public function subQuery(Closure $closure, ?string $alias = null, bool $isInterv $rawQuery = ($isIntervalQuery ? '(' : '') . $builder->generateSelectQuery() . ($isIntervalQuery ? ')' : '') - . ($alias !== null ? ' AS ' . $alias : ''); + . ($alias !== null ? ' AS ' . $this->driver->escapeIdentify($alias) : ''); return $this->raw($rawQuery); } @@ -1077,8 +1124,6 @@ public function group(Closure $closure, string $logical = 'AND'): self $builder = $this->clone(); call_user_func_array($closure, [$builder->resetStructure()]); - - foreach (['where', 'on', 'having'] as $stmt) { $statement = $builder->__generateStructure($stmt); !empty($statement) && $this->structure[$stmt][$logical][] = '(' . $statement . ')'; @@ -1214,6 +1259,7 @@ public function generateUpdateQuery(): string public function generateUpdateBatchQuery(string $referenceColumn): string { $update = []; + $this->driver->escapeIdentify($referenceColumn); $data = $this->structure['set']; $updateData = $columns = $where = []; foreach ($data as $set) { @@ -1283,6 +1329,7 @@ public function isBatch(): bool private function whereOrHavingStatementPrepare($column, $operator, $value): string { $operator = trim($operator); + is_string($column) && $this->driver->escapeIdentify($column); $column = (string)$column; if ($value !== null && in_array($operator, [ diff --git a/src/QueryBuilderFactory.php b/src/QueryBuilderFactory.php index 9e72ff0..2ee9245 100644 --- a/src/QueryBuilderFactory.php +++ b/src/QueryBuilderFactory.php @@ -7,7 +7,7 @@ * @author Muhammet ŞAFAK * @copyright Copyright © 2023 Muhammet ŞAFAK * @license ./LICENSE MIT - * @version 1.0 + * @version 1.0.1 * @link https://www.muhammetsafak.com.tr */ @@ -20,9 +20,9 @@ class QueryBuilderFactory implements QueryBuilderFactoryInterface /** * @inheritDoc */ - public function createQueryBuilder(): QueryBuilderInterface + public function createQueryBuilder(?string $driver = null): QueryBuilderInterface { - return new QueryBuilder(); + return new QueryBuilder($driver); } } \ No newline at end of file diff --git a/src/QueryBuilderFactoryInterface.php b/src/QueryBuilderFactoryInterface.php index eb160cd..11aea53 100644 --- a/src/QueryBuilderFactoryInterface.php +++ b/src/QueryBuilderFactoryInterface.php @@ -7,7 +7,7 @@ * @author Muhammet ŞAFAK * @copyright Copyright © 2023 Muhammet ŞAFAK * @license ./LICENSE MIT - * @version 1.0 + * @version 1.0.1 * @link https://www.muhammetsafak.com.tr */ @@ -17,9 +17,10 @@ interface QueryBuilderFactoryInterface { /** + * @param string|null $driver * @return QueryBuilderInterface * @throws */ - public function createQueryBuilder(): QueryBuilderInterface; + public function createQueryBuilder(?string $driver = null): QueryBuilderInterface; } diff --git a/tests/AbstractQueryBuilderDriverUnit.php b/tests/AbstractQueryBuilderDriverUnit.php new file mode 100644 index 0000000..dc7efaf --- /dev/null +++ b/tests/AbstractQueryBuilderDriverUnit.php @@ -0,0 +1,32 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace Test\InitORM\QueryBuilder; + +use InitORM\QueryBuilder\QueryBuilderFactory; +use InitORM\QueryBuilder\QueryBuilderInterface; +use PHPUnit\Framework\TestCase; + +class AbstractQueryBuilderDriverUnit extends TestCase +{ + protected QueryBuilderInterface $db; + + protected function setUp(): void + { + $factory = new QueryBuilderFactory(); + $this->db = $factory->createQueryBuilder('mysql'); + parent::setUp(); + } + +} \ No newline at end of file diff --git a/tests/DeleteQueryDriverUnitTest.php b/tests/DeleteQueryDriverUnitTest.php new file mode 100644 index 0000000..ca9effa --- /dev/null +++ b/tests/DeleteQueryDriverUnitTest.php @@ -0,0 +1,33 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace Test\InitORM\QueryBuilder; + +class DeleteQueryDriverUnitTest extends AbstractQueryBuilderDriverUnit +{ + + public function testDeleteStatementBuild() + { + + $this->db->from('post') + ->where('authorId', '=', 5) + ->limit(100); + + $expected = 'DELETE FROM `post` WHERE `authorId` = 5 LIMIT 100'; + + $this->assertEquals($expected, $this->db->generateDeleteQuery()); + $this->db->resetStructure(); + } + +} diff --git a/tests/DeleteQueryUnitTest.php b/tests/DeleteQueryUnitTest.php index d4e1b5a..61394af 100644 --- a/tests/DeleteQueryUnitTest.php +++ b/tests/DeleteQueryUnitTest.php @@ -30,4 +30,5 @@ public function testDeleteStatementBuild() $this->assertEquals($expected, $this->db->generateDeleteQuery()); $this->db->resetStructure(); } -} \ No newline at end of file + +} diff --git a/tests/InsertQueryDriverUnitTest.php b/tests/InsertQueryDriverUnitTest.php new file mode 100644 index 0000000..26109ec --- /dev/null +++ b/tests/InsertQueryDriverUnitTest.php @@ -0,0 +1,61 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace Test\InitORM\QueryBuilder; + +class InsertQueryDriverUnitTest extends AbstractQueryBuilderDriverUnit +{ + + + public function testInsertStatementBuild() + { + $this->db->from('post'); + + $data = [ + 'title' => 'Post Title', + 'content' => 'Post Content', + 'author' => 5, + 'status' => true, + ]; + $this->db->set($data); + + + $expected = 'INSERT INTO `post` (`title`, `content`, `author`, `status`) VALUES (:title, :content, 5, :status);'; + $this->assertEquals($expected, $this->db->generateInsertQuery()); + $this->db->resetStructure(); + } + + public function testInsertBatchStatementBuild() + { + + $this->db->from('post'); + + $this->db->set([ + 'title' => 'Post Title #1', + 'content' => 'Post Content #1', + 'author' => 5, + 'status' => true, + ]) + ->set([ + 'title' => 'Post Title #2', + 'content' => 'Post Content #2', + 'status' => false, + ]); + + $expected = 'INSERT INTO `post` (`title`, `content`, `author`, `status`) VALUES (:title, :content, 5, :status), (:title_1, :content_1, NULL, :status_1);'; + $this->assertEquals($expected, $this->db->generateBatchInsertQuery()); + $this->db->resetStructure(); + } + +} diff --git a/tests/SelectQueryDriverUnitTest.php b/tests/SelectQueryDriverUnitTest.php new file mode 100644 index 0000000..364ae5c --- /dev/null +++ b/tests/SelectQueryDriverUnitTest.php @@ -0,0 +1,375 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace Test\InitORM\QueryBuilder; + +use InitORM\QueryBuilder\QueryBuilder; + +class SelectQueryDriverUnitTest extends AbstractQueryBuilderDriverUnit +{ + + public function testSelectBuilder() + { + $this->db->select('id', 'name'); + $this->db->table('user'); + + $expected = "SELECT `id`, `name` FROM `user` WHERE 1"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testBlankBuild() + { + $this->db->from('post'); + + $expected = 'SELECT * FROM `post` WHERE 1'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSelfJoinBuild() + { + $this->db->select('post.id', 'post.title', 'user.name AS authorName') + ->table('post') + ->selfJoin('user', 'user.id = post.user'); + + $expected = "SELECT `post`.`id`, `post`.`title`, `user`.`name` AS `authorName` FROM `post`, `user` WHERE `user`.`id` = `post`.`user`"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testInnerJoinBuild() + { + $this->db->select('post.id, post.title', 'user.name as authorName') + ->from('post') + ->innerJoin('user', 'user.id = post.user'); + + $expected = "SELECT `post`.`id`, `post`.`title`, `user`.`name` as `authorName` FROM `post` INNER JOIN `user` ON `user`.`id` = `post`.`user` WHERE 1"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testLeftJoinBuild() + { + $this->db->select('post.id', 'post.title', 'user.name as authorName'); + $this->db->from('post'); + $this->db->leftJoin('user', 'user.id=post.user'); + + $expected = "SELECT `post`.`id`, `post`.`title`, `user`.`name` as `authorName` FROM `post` LEFT JOIN `user` ON `user`.`id`=`post`.`user` WHERE 1"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testRightJoinBuild() + { + $this->db->select('post.id', 'post.title', 'user.name as authorName'); + $this->db->from('post'); + $this->db->rightJoin('user', 'user.id=post.user'); + + $expected = "SELECT `post`.`id`, `post`.`title`, `user`.`name` as `authorName` FROM `post` RIGHT JOIN `user` ON `user`.`id`=`post`.`user` WHERE 1"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + + public function testLeftOuterJoinBuild() + { + $this->db->select('post.id', 'post.title', 'user.name as authorName'); + $this->db->from('post'); + $this->db->leftOuterJoin('user', 'user.id=post.user'); + + $expected = "SELECT `post`.`id`, `post`.`title`, `user`.`name` as `authorName` FROM `post` LEFT OUTER JOIN `user` ON `user`.`id`=`post`.`user` WHERE 1"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testRightOuterJoinBuild() + { + $this->db->select('post.id', 'post.title', 'user.name as authorName'); + $this->db->from('post'); + $this->db->rightOuterJoin('user', 'user.id=post.user'); + + $expected = "SELECT `post`.`id`, `post`.`title`, `user`.`name` as `authorName` FROM `post` RIGHT OUTER JOIN `user` ON `user`.`id`=`post`.`user` WHERE 1"; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testLimitStatement() + { + $this->db->select('id') + ->from('book') + ->limit(5); + + $expected = 'SELECT `id` FROM `book` WHERE 1 LIMIT 5'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testOffsetStatement() + { + $this->db->select('id') + ->from('book') + ->offset(5); + + $expected = 'SELECT `id` FROM `book` WHERE 1 OFFSET 5'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testOffsetLimitStatement() + { + $this->db->select('id') + ->from('book') + ->offset(50) + ->limit(25); + + $expected = 'SELECT `id` FROM `book` WHERE 1 LIMIT 50, 25'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testNegativeOffsetLimitStatement() + { + $this->db->select('id') + ->from('book') + ->offset(-25) + ->limit(-20); + + // If limit and offset are negative integers, their absolute values are taken. + $expected = 'SELECT `id` FROM `book` WHERE 1 LIMIT 25, 20'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSelectDistinctStatement() + { + $this->db->selectDistinct('name') + ->from('book'); + $expected = 'SELECT DISTINCT(`name`) FROM `book` WHERE 1'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSelectDistinctJoinStatement() + { + $this->db->selectDistinct('author.name') + ->from('book') + ->innerJoin('author', 'author.id=book.author'); + $expected = 'SELECT DISTINCT(`author`.`name`) FROM `book` INNER JOIN `author` ON `author`.`id`=`book`.`author` WHERE 1'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testOrderByStatement() + { + $this->db->select('name') + ->from('book') + ->orderBy('authorId', 'ASC') + ->orderBy('id', 'DESC') + ->limit(10); + + $expected = 'SELECT `name` FROM `book` WHERE 1 ORDER BY `authorId` ASC, `id` DESC LIMIT 10'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testWhereSQLFunctionStatementBuild() + { + $this->db->from('post') + ->andBetween('date', ['2022-05-07', 'CURDATE()']); + + $expected = 'SELECT * FROM `post` WHERE `date` BETWEEN :date AND CURDATE()'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testWhereRegexpSQLStatementBuild() + { + $this->db->from('post') + ->regexp('title', '^M[a-z]K$'); + + $expected = 'SELECT * FROM `post` WHERE `title` REGEXP :title'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSelectCoalesceSQLStatementBuild() + { + $this->db->select('post.title') + ->selectCoalesce('stat.view', 0) + ->from('post') + ->leftJoin('stat', 'stat.id=post.id') + ->where('post.id', 5); + + $expected = 'SELECT `post`.`title`, COALESCE(`stat`.`view`, 0) FROM `post` LEFT JOIN `stat` ON `stat`.`id`=`post`.`id` WHERE `post`.`id` = 5'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSelectCoalesceDefaultValue() + { + $this->db->select('post.title') + ->selectCoalesce('stat.view', 'post.view', 'views') + ->from('post') + ->leftJoin('stat', 'stat.id=post.id') + ->where('post.id', 5); + + $expected = 'SELECT `post`.`title`, COALESCE(`stat`.`view`, `post`.`view`) AS `views` FROM `post` LEFT JOIN `stat` ON `stat`.`id`=`post`.`id` WHERE `post`.`id` = 5'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + + public function testTableAliasSQLStatementBuild() + { + $this->db->select('p.title') + ->select('s.view as s_view') + ->from('post as p') + ->leftJoin('stat as s', 's.id=p.id') + ->where('p.id', 5); + + $expected = 'SELECT `p`.`title`, `s`.`view` as `s_view` FROM `post` as `p` LEFT JOIN `stat` as `s` ON `s`.`id`=`p`.`id` WHERE `p`.`id` = 5'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testTableJoinAliasSQLStatementBuild() + { + $this->db->select('p.title') + ->select('s.view as s_view') + ->from('post p') + ->leftJoin('stat s', 's.id=p.id') + ->where('p.id', 5); + + $expected = 'SELECT `p`.`title`, `s`.`view` as `s_view` FROM `post` `p` LEFT JOIN `stat` `s` ON `s`.`id`=`p`.`id` WHERE `p`.`id` = 5'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testWhereGroupStatement() + { + $this->db->select('id') + ->from('users') + ->where('status', 1) + ->group(function (QueryBuilder $builder) { + $builder->where('type', 3) + ->where('type', 4); + }); + + $expected = 'SELECT `id` FROM `users` WHERE `status` = 1 AND (`type` = 3 AND `type` = 4)'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testWhereGroupMultipleStatement() + { + $this->db->select('id, title, content, url') + ->from('posts') + ->where('status', 1) + ->group(function (QueryBuilder $db) { + $db->where('user_id', 1) + ->where('datetime', '>=', date("Y-m-d")); + }, 'or') + ->group(function (QueryBuilder $db) { + $db->group(function (QueryBuilder $db) { + $db->where('id', 2) + ->where('status', 3); + }, 'or') + ->group(function (QueryBuilder $db) { + $db->where('id', 4) + ->where('status', 5); + }, 'or'); + }, 'or'); + + $expected = 'SELECT `id`, `title`, `content`, `url` FROM `posts` WHERE `status` = 1 AND (`user_id` = 1 AND `datetime` >= :datetime) OR ((`id` = 2 AND `status` = 3) OR (`id` = 4 AND `status` = 5))'; + + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + + public function testJoinClosureGive() + { + $this->db->select('u.id', 'u.name', 'u.status, p.title') + ->from('users AS u') + ->where('u.status', 1) + ->join('posts AS p', function (QueryBuilder $builder) { + $builder->on('p.user_id', 'u.id') + ->where('p.publisher_time', '>=', $builder->raw('NOW()')); + }) + ->join('categories AS c', function (QueryBuilder $builder) { + $builder->on('c.id', 'p.category_id') + ->on('c.blog_id', 'u.blog_id') + ->where('c.status', 1) + ->having($builder->raw('COUNT(p.category_id) > 1')); + })->limit(5); + + $expected = 'SELECT `u`.`id`, `u`.`name`, `u`.`status`, `p`.`title` FROM `users` AS `u` INNER JOIN `posts` AS `p` ON `p`.`user_id` = `u`.`id` INNER JOIN `categories` AS `c` ON `c`.`id` = `p`.`category_id` AND `c`.`blog_id` = `u`.`blog_id` WHERE `u`.`status` = 1 AND `p`.`publisher_time` >= NOW() AND `c`.`status` = 1 HAVING COUNT(p.category_id) > 1 LIMIT 5'; + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSubQuery() + { + + $this->db->select('u.name') + ->from('users AS u') + ->whereIn('u.id', $this->db->subQuery(function (QueryBuilder $builder) { + $builder->select('id') + ->from('roles') + ->where('name', 'admin'); + })); + $expected = 'SELECT `u`.`name` FROM `users` AS `u` WHERE `u`.`id` IN (SELECT `id` FROM `roles` WHERE `name` = :name)'; + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + + public function testSubQueryJoinTable() + { + $this->db->select('u.name, p.title') + ->from('users AS u') + ->join($this->db->subQuery(function (QueryBuilder $builder) { + $builder->select('id, title, user_id') + ->from('posts') + ->where('user_id', 5); + }, 'p'), 'p.user_id = u.id', ''); + + $expected = 'SELECT `u`.`name`, `p`.`title` FROM `users` AS `u` JOIN (SELECT `id`, `title`, `user_id` FROM `posts` WHERE `user_id` = 5) AS `p` ON `p`.`user_id` = `u`.`id` WHERE 1'; + $this->assertEquals($expected, $this->db->generateSelectQuery()); + $this->db->resetStructure(); + } + +} diff --git a/tests/SelectQueryUnitTest.php b/tests/SelectQueryUnitTest.php index 838f621..0fb0c30 100644 --- a/tests/SelectQueryUnitTest.php +++ b/tests/SelectQueryUnitTest.php @@ -371,4 +371,5 @@ public function testSubQueryJoinTable() $this->assertEquals($expected, $this->db->generateSelectQuery()); $this->db->resetStructure(); } + } diff --git a/tests/UpdateQueryDriverUnitTest.php b/tests/UpdateQueryDriverUnitTest.php new file mode 100644 index 0000000..abe2cb3 --- /dev/null +++ b/tests/UpdateQueryDriverUnitTest.php @@ -0,0 +1,60 @@ + + * @copyright Copyright © 2023 Muhammet ŞAFAK + * @license ./LICENSE MIT + * @version 1.0.1 + * @link https://www.muhammetsafak.com.tr + */ + +declare(strict_types=1); +namespace Test\InitORM\QueryBuilder; + +class UpdateQueryDriverUnitTest extends AbstractQueryBuilderDriverUnit +{ + + public function testUpdateStatementBuild() + { + + $this->db->from('post') + ->where('status', '=', true) + ->limit(5); + + $data = [ + 'title' => 'New Title', + 'status' => false, + ]; + $this->db->set($data); + + $expected = 'UPDATE `post` SET `title` = :title, `status` = :status_1 WHERE `status` = :status LIMIT 5'; + + $this->assertEquals($expected, $this->db->generateUpdateQuery()); + $this->db->resetStructure(); + } + + public function testUpdateBatchStatementBuild() + { + + $this->db->from('post') + ->where('status', '=', true); + + $this->db->set([ + 'id' => 5, + 'title' => 'New Title #5', + 'content' => 'New Content #5', + ])->set([ + 'id' => 10, + 'title' => 'New Title #10', + ]); + + $expected = 'UPDATE `post` SET `title` = CASE WHEN `id` = 5 THEN :title WHEN `id` = 10 THEN :title_1 ELSE `title` END, `content` = CASE WHEN `id` = 5 THEN :content ELSE `content` END WHERE `status` = :status AND `id` IN (5, 10)'; + + $this->assertEquals($expected, $this->db->generateUpdateBatchQuery('id')); + $this->db->resetStructure(); + } + +}