diff --git a/README.md b/README.md index bbce377..5a1b23a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![GitHub license](https://img.shields.io/github/license/co0lc0der/simple-query-builder-php?style=flat-square)](https://github.com/co0lc0der/simple-query-builder-php/blob/main/LICENSE.md) ![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/co0lc0der/simple-query-builder?color=8993be&style=flat-square) -This is a small easy-to-use PHP component for working with a database by PDO. It provides some public methods to compose SQL queries and manipulate data. Each SQL query is prepared and safe. PDO (see `Connection` class) fetches data to _arrays_ by default. At present time the component supports MySQL and SQLite (file or memory). +This is a small easy-to-use PHP component for working with a database by PDO. It provides some public methods to compose SQL queries and manipulate data. Each SQL query is prepared and safe. QueryBuilder fetches data to _arrays_ by default. At present time the component supports MySQL and SQLite (file or memory). **PAY ATTENTION! v0.2 and v0.3+ are incompatible.** @@ -30,29 +30,7 @@ or add "co0lc0der/simple-query-builder": "*" ``` to the `require section` of your `composer.json` file. - ## How to use -### Main public methods -- `getSql()` returns SQL query string which will be executed -- `getParams()` returns an array of parameters for a query -- `getResult()` returns query's results -- `getCount()` returns results' rows count -- `hasError()` returns `true` if an error is had -- `getErrorMessage()` returns an error message if an error is had -- `setError($message)` sets `$error` to `true` and `$errorMessage` -- `getFirst()` returns the first item of results -- `getLast()` returns the last item of results -- `reset()` resets state to default values (except PDO property) -- `all()` executes SQL query and return all rows of result (`fetchAll()`) -- `one()` executes SQL query and return the first row of result (`fetch()`) -- `column($col)` executes SQL query and returns the needed column of result by its name, `col` is `'id'` by default -- `pluck($key, $col)` executes SQL query and returns an array (the key (usually ID) and the needed column of result) by their names, `key` is `id` and `col` is `''` by default -- `go()` this method is for non `SELECT` queries. it executes SQL query and return nothing (but returns the last inserted row ID for `INSERT` method) -- `count()` prepares a query with SQL `COUNT(*)` function and executes it -- `exists()` returns `true` if SQL query result has a row and `false` if it hasn't -- `query($sql, $params[], $fetch_type)` executes prepared `$sql` with `$params`. it can be used for custom queries -- 'SQL' methods are presented in [Usage section](#usage-examples) - ### Edit `config.php` and set the parameters up. Choose DB driver, DB name etc ```php $config = require_once __DIR__ . '/config.php'; @@ -72,332 +50,46 @@ $query = new QueryBuilder(Connection::make($config['database'])); // $printError $query = new QueryBuilder(Connection::make($config['database']), true) ``` ### Usage examples -- Select all rows from a table +#### Select all rows from a table ```php $results = $query->select('users')->all(); ``` +Result query ```sql SELECT * FROM `users`; ``` -- Select a row with a condition -```php -$results = $query->select('users')->where([['id', '=', 10]])->one(); -// or since 0.3.4 -$results = $query->select('users')->where([['id', 10]])->one(); -``` -```sql -SELECT * FROM `users` WHERE `id` = 10; -``` -- Select rows with two conditions +#### Select rows with two conditions ```php -$results = $query->select('users')->where([ - ['id', '>', 1], - 'and', - ['group_id', '=', 2], -])->all(); -// or since 0.3.4 $results = $query->select('users')->where([ ['id', '>', 1], 'and', ['group_id', 2], ])->all(); ``` +Result query ```sql SELECT * FROM `users` WHERE (`id` > 1) AND (`group_id` = 2); ``` -- Select a row with a `LIKE` and `NOT LIKE` condition -```php -$results = $query->select('users')->like(['name', '%John%'])->all(); -// or -$results = $query->select('users')->where([['name', 'LIKE', '%John%']])->all(); -// or since 0.3.6 -$results = $query->select('users')->like('name', '%John%')->all(); -``` -```sql -SELECT * FROM `users` WHERE (`name` LIKE '%John%'); -``` -```php -$results = $query->select('users')->notLike(['name', '%John%'])->all(); -// or -$results = $query->select('users')->where([['name', 'NOT LIKE', '%John%']])->all(); -// or since 0.3.6 -$results = $query->select('users')->notLike('name', '%John%')->all(); -``` -```sql -SELECT * FROM `users` WHERE (`name` NOT LIKE '%John%'); -``` -- Select a row with a `IS NULL` and `IS NOT NULL` condition (since 0.3.5) -```php -$results = $query->select('users')->isNull('phone')->all(); -# or -$results = $query->select('users')->where([['phone', 'is null']])->all(); -``` -```sql -SELECT * FROM `users` WHERE (`phone` IS NULL); -``` -```php -$results = $query->select('customers')->isNotNull('address')->all(); -# or -$results = $query->select('customers')->notNull('address')->all(); -# or -$results = $query->select('customers')->where([['address', 'is not null']])->all(); -``` -```sql -SELECT * FROM `customers` WHERE (`address` IS NOT NULL); -``` -- Select rows with `OFFSET` and `LIMIT` -```php -$results = $query->select('posts') - ->where([['user_id', '=', 3]]) - ->offset(14) - ->limit(7) - ->all(); -// or since 0.3.4 -$results = $query->select('posts') - ->where([['user_id', 3]]) - ->offset(14) - ->limit(7) - ->all(); -``` -```sql -SELECT * FROM `posts` WHERE (`user_id` = 3) OFFSET 14 LIMIT 7; -``` -- Select custom fields with additional SQL -1. `COUNT()` -```php -$results = $query->select('users', ['counter' => 'COUNT(*)'])->one(); -// or -$results = $query->count('users')->one(); -``` -```sql -SELECT COUNT(*) AS `counter` FROM `users`; -``` -2. `ORDER BY` -```php -$results = $query->select(['b' => 'branches'], ['b.id', 'b.name']) - ->where([['b.id', '>', 1], 'and', ['b.parent_id', '=', 1]]) - ->orderBy('b.id', 'desc') - ->all(); -// or since 0.3.4 -$results = $query->select(['b' => 'branches'], ['b.id', 'b.name']) - ->where([['b.id', '>', 1], 'and', ['b.parent_id', 1]]) - ->orderBy('b.id desc') - ->all(); -``` -```sql -SELECT `b`.`id`, `b`.`name` FROM `branches` AS `b` -WHERE (`b`.`id` > 1) AND (`b`.`parent_id` = 1) -ORDER BY `b`.`id` DESC; -``` -3. `GROUP BY` and `HAVING` -```php -$results = $query->select('posts', ['id', 'category', 'title']) - ->where([['views', '>=', 1000]]) - ->groupBy('category') - ->all(); -``` -```sql -SELECT `id`, `category`, `title` FROM `posts` -WHERE (`views` >= 1000) GROUP BY `category`; -``` -```php -$groups = $query->select('orders', ['month_num' => 'MONTH(`created_at`)', 'total' => 'SUM(`total`)']) - ->where([['YEAR(`created_at`)', '=', 2020]]) - ->groupBy('month_num') - ->having([['total', '=', 20000]]) - ->all(); -// or since 0.3.4 -$groups = $query->select('orders', ['month_num' => 'MONTH(`created_at`)', 'total' => 'SUM(`total`)']) - ->where([['YEAR(`created_at`)', 2020]]) - ->groupBy('month_num') - ->having([['total', 20000]]) - ->all(); -``` -```sql -SELECT MONTH(`created_at`) AS `month_num`, SUM(`total`) AS `total` -FROM `orders` WHERE (YEAR(`created_at`) = 2020) -GROUP BY `month_num` HAVING (`total` = 20000); -``` -4. `JOIN`. Supports `INNER`, `LEFT OUTER`, `RIGHT OUTER`, `FULL OUTER` and `CROSS` joins (`INNER` is by default) -```php -$results = $query->select(['u' => 'users'], [ - 'u.id', - 'u.email', - 'u.username', - 'perms' => 'groups.permissions' - ]) - ->join('groups', ['u.group_id', 'groups.id']) - ->limit(5) - ->all(); -``` -```sql -SELECT `u`.`id`, `u`.`email`, `u`.`username`, `groups`.`permissions` AS `perms` -FROM `users` AS `u` -INNER JOIN `groups` ON `u`.`group_id` = `groups`.`id` -LIMIT 5; -``` -```php -$results = $query->select(['cp' => 'cabs_printers'], [ - 'cp.id', - 'cp.cab_id', - 'cab_name' => 'cb.name', - 'cp.printer_id', - 'printer_name' => 'p.name', - 'cartridge_type' => 'c.name', - 'cp.comment' - ]) - ->join(['cb' => 'cabs'], ['cp.cab_id', 'cb.id']) - ->join(['p' => 'printer_models'], ['cp.printer_id', 'p.id']) - ->join(['c' => 'cartridge_types'], 'p.cartridge_id=c.id') - ->where([['cp.cab_id', 'in', [11, 12, 13]], 'or', ['cp.cab_id', '=', 5], 'and', ['p.id', '>', 'c.id']]) - ->all(); -``` -```sql -SELECT `cp`.`id`, `cp`.`cab_id`, `cb`.`name` AS `cab_name`, `cp`.`printer_id`, - `p`.`name` AS `printer_name`, `c`.`name` AS `cartridge_type`, `cp`.`comment` -FROM `cabs_printers` AS `cp` -INNER JOIN `cabs` AS `cb` ON `cp`.`cab_id` = `cb`.`id` -INNER JOIN `printer_models` AS `p` ON `cp`.`printer_id` = `p`.`id` -INNER JOIN `cartridge_types` AS `c` ON p.cartridge_id=c.id -WHERE (`cp`.`cab_id` IN (11,12,13)) OR (`cp`.`cab_id` = 5) AND (`p`.`id` > `c`.`id`) -``` -```php -// or since 0.3.4 -$results = $query->select(['cp' => 'cabs_printers'], [ - 'cp.id', - 'cp.cab_id', - 'cab_name' => 'cb.name', - 'cp.printer_id', - 'cartridge_id' => 'c.id', - 'printer_name' => 'p.name', - 'cartridge_type' => 'c.name', - 'cp.comment' - ]) - ->join(['cb' => 'cabs'], ['cp.cab_id', 'cb.id']) - ->join(['p' => 'printer_models'], ['cp.printer_id', 'p.id']) - ->join(['c' => 'cartridge_types'], ['p.cartridge_id', 'c.id']) - ->groupBy(['cp.printer_id', 'cartridge_id']) - ->orderBy(['cp.cab_id', 'cp.printer_id desc']) - ->all(); -``` -```sql -SELECT `cp`.`id`, `cp`.`cab_id`, `cb`.`name` AS `cab_name`, `cp`.`printer_id`, `c`.`id` AS `cartridge_id`, - `p`.`name` AS `printer_name`, `c`.`name` AS `cartridge_type`, `cp`.`comment` -FROM `cabs_printers` AS `cp` -INNER JOIN `cabs` AS `cb` ON `cp`.`cab_id` = `cb`.`id` -INNER JOIN `printer_models` AS `p` ON `cp`.`printer_id` = `p`.`id` -INNER JOIN `cartridge_types` AS `c` ON `p`.`cartridge_id` = `c`.`id` -GROUP BY `cp`.`printer_id`, `cartridge_id` -ORDER BY `cp`.`cab_id` ASC, `cp`.`printer_id` DESC; -``` -- Insert a row -```php -$new_id = $query->insert('groups', [ - 'name' => 'Moderator', - 'permissions' => 'moderator' -])->go(); -``` -```sql -INSERT INTO `groups` (`name`, `permissions`) VALUES ('Moderator', 'moderator'); -``` -- Insert many rows -```php -$query->insert('groups', [ - ['name', 'role'], - ['Moderator', 'moderator'], - ['Moderator2', 'moderator'], - ['User', 'user'], - ['User2', 'user'], -])->go(); -``` -```sql -INSERT INTO `groups` (`name`, `role`) -VALUES ('Moderator', 'moderator'), - ('Moderator2', 'moderator'), - ('User', 'user'), - ('User2', 'user'); -``` -- Update a row -```php -$query->update('users', [ - 'username' => 'John Doe', - 'status' => 'new status' - ]) - ->where([['id', '=', 7]]) - ->limit() - ->go(); -// or since 0.3.4 -$query->update('users', [ - 'username' => 'John Doe', - 'status' => 'new status' - ]) - ->where([['id', 7]]) - ->limit() - ->go(); -``` -```sql -UPDATE `users` SET `username` = 'John Doe', `status` = 'new status' -WHERE `id` = 7 LIMIT 1; -``` -- Update rows +#### Update a row ```php $query->update('posts', ['status' => 'published']) ->where([['YEAR(`updated_at`)', '>', 2020]]) ->go(); ``` +Result query ```sql UPDATE `posts` SET `status` = 'published' WHERE (YEAR(`updated_at`) > 2020); ``` -- Delete a row -```php -$query->delete('users') - ->where([['name', '=', 'John']]) - ->limit() - ->go(); -// or since 0.3.4 -$query->delete('users') - ->where([['name', 'John']]) - ->limit() - ->go(); -``` -```sql -DELETE FROM `users` WHERE `name` = 'John' LIMIT 1; -``` -- Delete rows -```php -$query->delete('comments') - ->where([['user_id', '=', 10]]) - ->go(); -// or since 0.3.4 -$query->delete('comments') - ->where([['user_id', 10]]) - ->go(); -``` -```sql -DELETE FROM `comments` WHERE `user_id` = 10; -``` -- Truncate a table +More examples you can find in [documentation](https://github.com/co0lc0der/simple-query-builder-php/blob/main/docs/index.md) or tests. -This method will be moved to another class -```php -$query->truncate('users')->go(); -``` -```sql -TRUNCATE TABLE `users`; -``` -- Drop a table - -This method will be moved to another class -```php -$query->drop('temporary')->go(); // $add_exists = true -``` -```sql -DROP TABLE IF EXISTS `temporary`; -``` -```php -$query->drop('temp', false)->go(); // $add_exists = false -``` -```sql -DROP TABLE `temp`; -``` +## ToDo +I'm going to add the next features into future versions +- write more unit testes +- add subqueries for QueryBuilder +- add `BETWEEN` +- add `WHERE EXISTS` +- add TableBuilder class (for beginning `CREATE TABLE`, move `$query->drop()` and `$query->truncate()` into it) +- add PostgreSQL support +- add `WITH` +- and probably something more diff --git a/ToDo.md b/ToDo.md new file mode 100644 index 0000000..bdba14a --- /dev/null +++ b/ToDo.md @@ -0,0 +1,10 @@ +# ToDo +I'm going to add the next features into future versions +- write more unit testes +- add subqueries for QueryBuilder +- add `BETWEEN` +- add `WHERE EXISTS` +- add TableBuilder class (for beginning `CREATE TABLE`, move `$query->drop()` and `$query->truncate()` into it) +- add PostgreSQL support +- add `WITH` +- and probably something more diff --git a/composer.json b/composer.json index a958f8f..834e937 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,11 @@ "type": "library", "require": { "php": ">=7.4", - "ext-pdo": "*" + "ext-pdo": "*", + "ext-fileinfo": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-mbstring": "*" }, "license": "MIT", "autoload": { @@ -21,5 +25,8 @@ "minimum-stability": "stable", "require-dev": { "phpunit/phpunit": "^9.5" + }, + "scripts": { + "test": "phpunit" } } diff --git a/docs/Delete.md b/docs/Delete.md new file mode 100644 index 0000000..da5d610 --- /dev/null +++ b/docs/Delete.md @@ -0,0 +1,49 @@ +# DELETE +## Delete a row +```php +$query->delete('users') + ->where([['name', '=', 'John']]) + ->go(); +``` +or since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$query->delete('users') + ->where([['name', 'John']]) + ->go(); +``` +Result query +```sql +DELETE FROM `users` WHERE `name` = 'John'; +``` +## Delete a row with `LIMIT` +**Pay attention!** SQLite doesn't support this mode. +```php +$query->delete('users') + ->where([['name', 'John']]) + ->limit() + ->go(); +``` +Result query +```sql +DELETE FROM `users` WHERE `name` = 'John' LIMIT 1; +``` +## Delete rows +```php +$query->delete('comments') + ->where([['user_id', '=', 10]]) + ->go(); +``` +or since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$query->delete('comments') + ->where([['user_id', 10]]) + ->go(); +``` +Result query +```sql +DELETE FROM `comments` WHERE `user_id` = 10; +``` + +To the [TABLE section](Table.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Init.md b/docs/Init.md new file mode 100644 index 0000000..6b0af09 --- /dev/null +++ b/docs/Init.md @@ -0,0 +1,58 @@ +# Initialization +## Edit `config.php` and set the parameters up. Choose DB driver, DB name etc +```php +$config = require_once __DIR__ . '/config.php'; +``` +#### Config example for SQLite DB in memory +```php +// config.php +return [ + 'database' => [ + 'driver' => 'memory', + ] +]; +``` +#### Config example for SQLite DB file +```php +// config.php +return [ + 'database' => [ + 'driver' => 'sqlite', + 'dbname' => 'db.db', + 'username' => '', + 'password' => '', + ] +]; +``` +#### Config example for MySQL +```php +// config.php +return [ + 'database' => [ + 'driver' => 'mysql', + 'dbhost' => 'localhost', + 'dbname' => _DATABASENAME_, + 'username' => _DBUSERNAME_, + 'password' => _DBPASSWORD_, + 'charset' => 'utf8', + ] +]; +``` +## Use composer autoloader +```php +require_once __DIR__ . '/vendor/autoload.php'; + +use co0lc0der\QueryBuilder\Connection; +use co0lc0der\QueryBuilder\QueryBuilder; +``` +## Init `QueryBuilder` with `Connection::make()` +```php +$query = new QueryBuilder(Connection::make($config['database'])); // $printErrors = false + +// for printing errors (since 0.3.6) +$query = new QueryBuilder(Connection::make($config['database']), true) +``` + +To the [Methods section](Methods.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Insert.md b/docs/Insert.md new file mode 100644 index 0000000..f821691 --- /dev/null +++ b/docs/Insert.md @@ -0,0 +1,34 @@ +# INSERT +## Insert a row +```php +$new_id = $query->insert('groups', [ + 'name' => 'Moderator', + 'permissions' => 'moderator' +])->go(); +``` +Result query +```sql +INSERT INTO `groups` (`name`, `permissions`) VALUES ('Moderator', 'moderator'); +``` +## Insert many rows +```php +$query->insert('groups', [ + ['name', 'role'], + ['Moderator', 'moderator'], + ['Moderator2', 'moderator'], + ['User', 'user'], + ['User2', 'user'], +])->go(); +``` +Result query +```sql +INSERT INTO `groups` (`name`, `role`) +VALUES ('Moderator', 'moderator'), + ('Moderator2', 'moderator'), + ('User', 'user'), + ('User2', 'user'); +``` + +To the [UPDATE section](Update.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Install.md b/docs/Install.md new file mode 100644 index 0000000..ad69e58 --- /dev/null +++ b/docs/Install.md @@ -0,0 +1,16 @@ +# Installation +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run +```bash +composer require co0lc0der/simple-query-builder +``` +or add +```json +"co0lc0der/simple-query-builder": "*" +``` +to the `require section` of your `composer.json` file. + +To the [Init section](Init.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Methods.md b/docs/Methods.md new file mode 100644 index 0000000..75d8fd4 --- /dev/null +++ b/docs/Methods.md @@ -0,0 +1,25 @@ +# Main public methods +## QueryBuilder class +- `query($sql, $params[], $fetch_type)` executes prepared `$sql` with `$params`. it can be used for custom queries +- `getSql()` returns SQL query string which will be executed +- `getParams()` returns an array of parameters for a query +- `getResult()` returns query's results +- `getCount()` returns results' rows count +- `getDriver()` returns DB driver name in lowercase +- `hasError()` returns `true` if an error is had +- `getErrorMessage()` returns an error message if an error is had +- `setError($message)` sets `$error` to `true` and `$errorMessage` +- `getFirst()` returns the first item of results +- `getLast()` returns the last item of results +- `reset()` resets state to default values (except PDO property) +- `all()` executes SQL query and returns **all rows** of result (`fetchAll()`) +- `one()` executes SQL query and returns **the first row** of result (`fetch()`) +- `column($col)` executes SQL query and returns the needed column of result by its name, `col` is `'id'` by default +- `pluck($key, $col)` executes SQL query and returns an array (the key (usually ID) and the needed column of result) by their names, `key` is `id` and `col` is `''` by default +- `go()` this method is for non `SELECT` queries. it executes SQL query and returns nothing (but returns the last inserted row ID for `INSERT` method) +- `count()` prepares a query with SQL `COUNT(*)` function and _executes it_ +- `exists()` returns `true` if SQL query has at least one row and `false` if it hasn't + +'SQL' methods are presented in the [next section](Select.md). + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Select.md b/docs/Select.md new file mode 100644 index 0000000..d67e002 --- /dev/null +++ b/docs/Select.md @@ -0,0 +1,220 @@ +# SELECT +More examples you can find in tests. +## Simple queries +### Selects with no tables +#### Math +```php +$results = $query->select("1+5 as 'res'")->one(); +``` +Result query +```sql +SELECT 1+5 as 'res'; +``` +#### Substring +```php +$results = $query->select("substr('Hello world!', 1, 5) as 'str'")->one(); +``` +Result query +```sql +SELECT substr('Hello world!', 1, 5) as 'str'; +``` +#### Current date and time +```php +$results = $query->select("strftime('%Y-%m-%d %H:%M', 'now')")->one(); +``` +Result query +```sql +SELECT strftime('%Y-%m-%d %H:%M', 'now'); +``` +#### SQLite functions +```php +$results = $query->select("sqlite_version() as ver")->one(); +``` +Result query +```sql +SELECT sqlite_version() as ver; +``` +### Select all rows from a table +```php +$results = $query->select('users')->all(); +``` +Result query +```sql +SELECT * FROM `users`; +``` +### Select a row with a condition +```php +$results = $query->select('users')->where([['id', '=', 10]])->one(); +``` +It's able not using equals `=` in `WHERE` conditions since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$results = $query->select('users')->where([['id', 10]])->one(); +``` +Result query +```sql +SELECT * FROM `users` WHERE `id` = 10; +``` +### Select rows with two conditions +```php +$results = $query->select('users')->where([['id', '>', 1], 'and', ['group_id', '=', 2]])->all(); +``` +or since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$results = $query->select('users')->where([['id', '>', 1], 'and', ['group_id', 2]])->all(); +``` +Result query +```sql +SELECT * FROM `users` WHERE (`id` > 1) AND (`group_id` = 2); +``` +### Select a row with a `LIKE` and `NOT LIKE` condition +```php +$results = $query->select('users')->like(['name', '%John%'])->all(); + +# or with WHERE +$results = $query->select('users')->where([['name', 'LIKE', '%John%']])->all(); +``` +or it's able to use two strings (instead of an array) in parameters since [v0.3.5](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.5) +```php +$results = $query->select('users')->like('name', '%John%')->all(); +``` +Result query +```sql +SELECT * FROM `users` WHERE (`name` LIKE '%John%'); +``` +```php +$results = $query->select('users')->notLike(['name', '%John%'])->all(); + +# or with WHERE +$results = $query->select('users')->where([['name', 'NOT LIKE', '%John%']])->all(); +``` +or it's able to use two strings (instead of an array) in parameters since [v0.3.5](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.5) +```php +$results = $query->select('users')->notLike('name', '%John%')->all(); +``` +Result query +```sql +SELECT * FROM `users` WHERE (`name` NOT LIKE '%John%'); +``` +### Select a row with a `IS NULL` and `IS NOT NULL` condition +since [v0.3.5](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.5) +```php +$results = $query->select('users')->isNull('phone')->all(); + +# or with WHERE +$results = $query->select('users')->where([['phone', 'is null']])->all(); +``` +Result query +```sql +SELECT * FROM `users` WHERE (`phone` IS NULL); +``` +```php +$results = $query->select('customers')->isNotNull('address')->all(); + +# or +$results = $query->select('customers')->notNull('address')->all(); + +# or with WHERE +$results = $query->select('customers')->where([['address', 'is not null']])->all(); +``` +Result query +```sql +SELECT * FROM `customers` WHERE (`address` IS NOT NULL); +``` +### Select rows with `OFFSET` and `LIMIT` +```php +$results = $query->select('posts') + ->where([['user_id', '=', 3]]) + ->offset(14) + ->limit(7) + ->all(); +``` +It's able not using equals `=` in `WHERE` conditions since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$results = $query->select('posts') + ->where([['user_id', 3]]) + ->offset(14) + ->limit(7) + ->all(); +``` +Result query +```sql +SELECT * FROM `posts` WHERE (`user_id` = 3) OFFSET 14 LIMIT 7; +``` +### Select custom fields with additional SQL +#### `COUNT()` +```php +$results = $query->select('users', ['counter' => 'COUNT(*)'])->one(); + +# or +$results = $query->count('users'); +``` +Result query +```sql +SELECT COUNT(*) AS `counter` FROM `users`; +``` +#### `ORDER BY` +```php +$results = $query->select(['b' => 'branches'], ['b.id', 'b.name']) + ->where([['b.id', '>', 1], 'and', ['b.parent_id', 1]]) + ->orderBy('b.id', 'desc') + ->all(); +``` +It's able not using equals `=` in `WHERE` conditions since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$results = $query->select(['b' => 'branches'], ['b.id', 'b.name']) + ->where([['b.id', '>', 1], 'and', ['b.parent_id', 1]]) + ->orderBy('b.id desc') + ->all(); +``` +Result query +```sql +SELECT `b`.`id`, `b`.`name` FROM `branches` AS `b` +WHERE (`b`.`id` > 1) AND (`b`.`parent_id` = 1) +ORDER BY `b`.`id` DESC; +``` +#### `DISTINCT` +```php +$results = $query->select('customers', ['city', 'country'], true)->orderBy('country desc')->all(); +``` +Result query +```sql +SELECT DISTINCT `city`, `country` FROM `customers` ORDER BY `country` DESC; +``` +#### `GROUP BY` and `HAVING` +```php +$results = $query->select('posts', ['id', 'category', 'title']) + ->where([['views', '>=', 1000]]) + ->groupBy('category') + ->all(); +``` +Result query +```sql +SELECT `id`, `category`, `title` FROM `posts` +WHERE (`views` >= 1000) GROUP BY `category`; +``` +More complicated example +```php +$results = $query->select('orders', ['month_num' => 'MONTH(`created_at`)', 'total' => 'SUM(`total`)']) + ->where([['YEAR(`created_at`)', '=', 2020]]) + ->groupBy('month_num') + ->having([['total', '=', 20000]]) + ->all(); +``` +It's able not using equals `=` in `HAVING` conditions since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$results = $query->select('orders', ['month_num' => 'MONTH(`created_at`)', 'total' => 'SUM(`total`)']) + ->where([['YEAR(`created_at`)', 2020]]) + ->groupBy('month_num') + ->having([['total', 20000]]) + ->all(); +``` +Result query +```sql +SELECT MONTH(`created_at`) AS `month_num`, SUM(`total`) AS `total` +FROM `orders` WHERE (YEAR(`created_at`) = 2020) +GROUP BY `month_num` HAVING (`total` = 20000); +``` + +To the [SELECT extensions section](Select_ext.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Select_ext.md b/docs/Select_ext.md new file mode 100644 index 0000000..d8c2e30 --- /dev/null +++ b/docs/Select_ext.md @@ -0,0 +1,193 @@ +# SELECT extensions +## `JOIN` +SQLite supports `INNER`, `LEFT OUTER` and `CROSS` joins (`INNER` is by default) +### `INNER JOIN` +```php +$results = $query->select(['u' => 'users'], [ + 'u.id', + 'u.email', + 'u.username', + 'perms' => 'groups.permissions' + ]) + ->join('groups', ['u.group_id', 'groups.id']) + ->limit(5) + ->all(); +``` +Result query +```sql +SELECT `u`.`id`, `u`.`email`, `u`.`username`, `groups`.`permissions` AS `perms` +FROM `users` AS `u` +INNER JOIN `groups` ON `u`.`group_id` = `groups`.`id` +LIMIT 5; +``` +More complicated examples +```php +$results = $query->select(['cp' => 'cabs_printers'], [ + 'cp.id', + 'cp.cab_id', + 'cab_name' => 'cb.name', + 'cp.printer_id', + 'printer_name' => 'p.name', + 'cartridge_type' => 'c.name', + 'cp.comment' + ]) + ->join({'cb': 'cabs'}, ['cp.cab_id', 'cb.id']) + ->join({'p': 'printer_models'}, ['cp.printer_id', 'p.id']) + ->join({'c': 'cartridge_types'}, 'p.cartridge_id=c.id') + ->where([['cp.cab_id', 'in', [11, 12, 13]], 'or', ['cp.cab_id', '=', 5], 'and', ['p.id', '>', 'c.id']]) + ->all(); +``` +Result query +```sql +SELECT `cp`.`id`, `cp`.`cab_id`, `cb`.`name` AS `cab_name`, `cp`.`printer_id`, + `p`.`name` AS `printer_name`, `c`.`name` AS `cartridge_type`, `cp`.`comment` +FROM `cabs_printers` AS `cp` +INNER JOIN `cabs` AS `cb` ON `cp`.`cab_id` = `cb`.`id` +INNER JOIN `printer_models` AS `p` ON `cp`.`printer_id` = `p`.`id` +INNER JOIN `cartridge_types` AS `c` ON p.cartridge_id=c.id +WHERE (`cp`.`cab_id` IN (11, 12, 13)) OR (`cp`.`cab_id` = 5) AND (`p`.`id` > `c`.`id`); +``` +It's able not using equals `=` in `JOIN` conditions since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$results = $query->select(['cp' => 'cabs_printers'], [ + 'cp.id', + 'cp.cab_id', + 'cab_name' => 'cb.name', + 'cp.printer_id', + 'cartridge_id' => 'c.id', + 'printer_name' => 'p.name', + 'cartridge_type' => 'c.name', + 'cp.comment' + ]) + ->join(['cb' => 'cabs'], ['cp.cab_id', 'cb.id']) + ->join(['p' => 'printer_models'], ['cp.printer_id', 'p.id']) + ->join(['c' => 'cartridge_types'], ['p.cartridge_id', 'c.id']) + ->groupBy(['cp.printer_id', 'cartridge_id']) + ->orderBy(['cp.cab_id', 'cp.printer_id desc']) + ->all(); +``` +Result query +```sql +SELECT `cp`.`id`, `cp`.`cab_id`, `cb`.`name` AS `cab_name`, `cp`.`printer_id`, `c`.`id` AS `cartridge_id`, + `p`.`name` AS `printer_name`, `c`.`name` AS `cartridge_type`, `cp`.`comment` +FROM `cabs_printers` AS `cp` +INNER JOIN `cabs` AS `cb` ON `cp`.`cab_id` = `cb`.`id` +INNER JOIN `printer_models` AS `p` ON `cp`.`printer_id` = `p`.`id` +INNER JOIN `cartridge_types` AS `c` ON `p`.`cartridge_id` = `c`.`id` +GROUP BY `cp`.`printer_id`, `cartridge_id` +ORDER BY `cp`.`cab_id` ASC, `cp`.`printer_id` DESC; +``` +### `LEFT [OUTER] JOIN` +```php +# LEFT JOIN +$results = $query->select('employees', ['employees.employee_id', 'employees.last_name', 'positions.title']) + ->join('positions', ['employees.position_id', 'positions.position_id'], "left") + ->all(); + +# or LEFT OUTER JOIN +$results = $query->select('employees', ['employees.employee_id', 'employees.last_name', 'positions.title']) + ->join('positions', ['employees.position_id', 'positions.position_id'], "left outer") + ->all(); +``` +Result query +```sql +SELECT `employees`.`employee_id`, `employees`.`last_name`, `positions`.`title` FROM `employees` +LEFT [OUTER] JOIN `positions` ON `employees`.`position_id` = `positions`.`position_id`; +``` +## `INTERSECT` +Since [v0.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.4) +```php +$results = $query->select('departments', ['department_id']) + ->intersectSelect('employees')->all(); +``` +Result query +```sql +SELECT `department_id` FROM `departments` +INTERSECT +SELECT `department_id` FROM `employees`; +``` +One more example +```php +$results = $query->select('contacts', ['contact_id', 'last_name', 'first_name']) + ->where([['contact_id', '>', 50]]) + ->intersect() + ->select('customers', ['customer_id', 'last_name', 'first_name']) + ->where([['last_name', '<>', 'Zagoskin']]) + ->all(); +``` +Result query +```sql +SELECT `contact_id`, `last_name`, `first_name` FROM `contacts` WHERE (`contact_id` > 50) +INTERSECT +SELECT `customer_id`, `last_name`, `first_name` FROM `customers` WHERE (`last_name` <> 'Zagoskin'); +``` +## `EXCEPT` +Since [v0.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.4) +```php +$results = $query->select('departments', ['department_id']) + ->exceptSelect('employees')->all(); +``` +Result query +```sql +SELECT `department_id` FROM `departments` +EXCEPT +SELECT `department_id` FROM `employees`; +``` +One more example +```php +$results = $query->select('suppliers', ['supplier_id', 'state']) + ->where([['state', 'Nevada']]) + ->excepts() + ->select('companies', ['company_id', 'state']) + ->where([['company_id', '<', 2000]]) + ->orderBy('1 desc') + ->all(); +``` +Result query +```sql +SELECT `supplier_id`, `state` FROM `suppliers` WHERE (`state` = 'Nevada') +EXCEPT +SELECT `company_id`, `state` FROM `companies` WHERE (`company_id` < 2000) ORDER BY `1` DESC; +``` +## `UNION` and `UNION ALL` +Since [v0.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.4) +### `UNION` +```php +$results = $query->select('clients', ['name', 'age']) + ->union() + ->select('employees', ['name', 'age']) + ->all(); + +# or +$results = $query->select('clients', ['name', 'age']) + ->unionSelect('employees') + ->all(); +``` +Result query +```sql +SELECT `name`, `age` FROM `clients` +UNION +SELECT `name`, `age` FROM `employees`; +``` +### `UNION ALL` +```php +$results = $query->select('clients', ['name', 'age']) + ->union(true) + ->select('employees', ['name', 'age']) + ->all(); + +# or +$results = $query->select('clients', ['name', 'age']) + ->unionSelect('employees', true) + ->all(); +``` +Result query +```sql +SELECT `name`, `age` FROM `clients` +UNION ALL +SELECT `name`, `age` FROM `employees`; +``` + +To the [INSERT section](Insert.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Table.md b/docs/Table.md new file mode 100644 index 0000000..2d26b16 --- /dev/null +++ b/docs/Table.md @@ -0,0 +1,35 @@ +# TABLE +## TRUNCATE +Truncate a table + +**! This method will be moved into _TableBuilder_ class !** +```php +$query->truncate('users')->go() +``` +Result query +```sql +TRUNCATE TABLE `users`; +``` +## DROP +- Drop a table + +**! This method will be moved into _TableBuilder_ class !** +```php +$query->drop('temporary')->go() +``` +Result query +```sql +DROP TABLE IF EXISTS `temporary`; +``` +- Without `IF EXISTS` +```php +$query->drop('temporary', false)->go() +``` +Result query +```sql +DROP TABLE `temporary`; +``` + +To the [View section](View.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/Update.md b/docs/Update.md new file mode 100644 index 0000000..0bf0e7f --- /dev/null +++ b/docs/Update.md @@ -0,0 +1,41 @@ +# UPDATE +## Update a row +```php +$query->update('users', [ + 'username' => 'John Doe', + 'status' => 'new status' + ]) + ->where([['id', '=', 7]]) + ->limit() + ->go(); +``` +or since [v0.3.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.3.4) +```php +$query->update('users', [ + 'username' => 'John Doe', + 'status' => 'new status' + ]) + ->where([['id', 7]]) + ->limit() + ->go(); +``` +Result query +```sql +UPDATE `users` SET `username` = 'John Doe', `status` = 'new status' +WHERE `id` = 7 LIMIT 1; +``` +## Update rows +```php +$query->update('posts', ['status' => 'published']) + ->where([['YEAR(`updated_at`)', '>', 2020]]) + ->go(); +``` +Result query +```sql +UPDATE `posts` SET `status` = 'published' +WHERE (YEAR(`updated_at`) > 2020); +``` + +To the [DELETE section](Delete.md) + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/View.md b/docs/View.md new file mode 100644 index 0000000..c34dc62 --- /dev/null +++ b/docs/View.md @@ -0,0 +1,55 @@ +# VIEW +Since [v0.4](https://github.com/co0lc0der/simple-query-builder-php/releases/tag/v0.4) +## CREATE +Create a view from SELECT query +```php +$query->select('users') + ->where([['email', 'is null'], 'or', ['email', '']]) + ->createView('users_no_email') + ->go(); +``` +Result query +```sql +CREATE VIEW IF NOT EXISTS `users_no_email` AS SELECT * FROM `users` WHERE (`email` IS NULL) OR (`email` = ''); +``` +One more example +```php +$query->select('users') + ->isNull('email') + ->createView('users_no_email') + ->go() +``` +Result query +```sql +CREATE VIEW IF NOT EXISTS `users_no_email` AS SELECT * FROM `users` WHERE (`email` IS NULL); +``` +- Without `IF EXISTS` +```php +$query->select('users') + ->isNull('email') + ->createView('users_no_email', false) + ->go(); +``` +Result query +```sql +CREATE VIEW `users_no_email` AS SELECT * FROM `users` WHERE (`email` IS NULL); +``` +## DROP +- Drop a view +```php +$query->dropView('users_no_email')->go(); +``` +Result query +```sql +DROP VIEW IF EXISTS `users_no_email`; +``` +- Without `IF EXISTS` +```php +$query->dropView('users_no_email', false)->go() +``` +Result query +```sql +DROP VIEW `users_no_email`; +``` + +Back to [doc index](index.md) or [readme](../README.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..ae97b65 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,20 @@ +# Index + +[![Latest Version](https://img.shields.io/github/release/co0lc0der/simple-query-builder-php?style=flat-square)](https://github.com/co0lc0der/simple-query-builder-php/release) +![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/co0lc0der/simple-query-builder?color=8993be&style=flat-square) + +- [Install](Install.md) +- [Initialization](Init.md) +- Main [public methods](Methods.md) +- Data manipulation + * [SELECT](Select.md) + + [SELECT extensions](Select_ext.md) (JOIN, UNION etc) + * [INSERT](Insert.md) + * [UPDATE](Update.md) + * [DELETE](Delete.md) +- Tables + * [TABLE](Table.md) +- Views + * [VIEW](View.md) + +Go to [readme](../README.md) diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 1aa26ce..6b64d79 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -8,10 +8,12 @@ */ class QueryBuilder { - private const OPERATORS = ['=', '>', '<', '>=', '<=', '!=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN']; + private const COND_OPERATORS = ['=', '>', '<', '>=', '<=', '!=', '<>', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN']; private const LOGICS = ['AND', 'OR', 'NOT']; private const SORT_TYPES = ['ASC', 'DESC']; - private const JOIN_TYPES = ['INNER', 'LEFT OUTER', 'RIGHT OUTER', 'FULL OUTER', 'CROSS']; + private const JOIN_TYPES = ['INNER', 'LEFT', 'LEFT OUTER', 'RIGHT OUTER', 'FULL OUTER', 'CROSS']; + private const SQLITE_JOIN_TYPES = ['INNER', 'LEFT', 'LEFT OUTER', 'CROSS']; + private const FIELD_SPEC_CHARS = ['+', '-', '*', '/', '%', '(', ')', '||']; private const NO_FETCH = 0; private const FETCH_ONE = 1; private const FETCH_ALL = 2; @@ -24,7 +26,9 @@ class QueryBuilder private bool $printErrors = false; private array $result = []; private array $params = []; + private $fields = []; private int $count = -1; + private bool $concat = false; /** * @param PDO $pdo @@ -156,10 +160,12 @@ public function reset(): bool { $this->sql = ''; $this->params = []; + $this->fields = []; $this->query = null; $this->result = []; $this->count = -1; $this->setError(); + $this->concat = false; return true; } @@ -215,7 +221,7 @@ public function count($table, string $field = '') /** * @param string $column - * @return $this|string|array + * @return QueryBuilder|string|array */ public function column(string $column = 'id') { @@ -409,7 +415,7 @@ private function prepareConditions($where): array $operator = strtoupper($cond[1]); $value = $cond[2]; - if (in_array($operator, self::OPERATORS)) { + if (in_array($operator, self::COND_OPERATORS)) { if ($operator == 'IN' && is_array($value)) { $values = rtrim(str_repeat("?,", count($value)), ','); $sql .= "({$field} {$operator} ({$values}))"; @@ -444,6 +450,42 @@ private function prepareConditions($where): array return $result; } + /** + * @param string $str + * @return bool + */ + private function searchForSpecChars(string $str): bool + { + foreach (self::FIELD_SPEC_CHARS as $char) { + if (mb_strpos($str, $char) !== false) { + return true; + } + } + + return false; + } + + /** + * @param $table + * @return string + */ + private function prepareTables($table): string + { + if (empty($table)) { + $this->setError('Empty $table in ' . __METHOD__); + return ''; + } + + if (is_string($table) && (mb_strpos(mb_strtolower($table), 'select') !== false)) { + $this->concat = true; + return "({$table})"; + } elseif (is_string($table) && $this->searchForSpecChars($table)) { + return $table; + } + + return $this->prepareAliases($table); + } + /** * @param string $field * @param string $sort @@ -452,16 +494,16 @@ private function prepareConditions($where): array private function prepareSorting(string $field = '', string $sort = ''): array { if (strpos($field, ' ') !== false) { - $splitted = explode(' ', $field); - $field = $splitted[0]; - $sort = $splitted[1]; + $splitted = explode(' ', $field); + $field = $splitted[0]; + $sort = $splitted[1]; } - $field = $this->prepareField($field); + $field = $this->prepareField($field); $sort = ($sort == '') ? 'ASC' : strtoupper($sort); - return [$field, $sort]; + return [$field, $sort]; } /** @@ -526,23 +568,29 @@ public function select($table, $fields = '*', $dist = false): QueryBuilder return $this; } - $this->reset(); + $preparedTable = $this->prepareTables($table); + $preparedFields = $this->prepareAliases($fields); - $this->sql = "SELECT "; - $this->sql .= $dist ? "DISTINCT " : ''; + if (!$this->concat) { + $this->reset(); + } - if (is_array($fields) || is_string($fields)) { - $this->sql .= $this->prepareAliases($fields); - } else { - $this->setError('Incorrect type of $fields in ' . __METHOD__ . '. $fields must be a string or an array'); - return $this; - } + $sql = "SELECT "; + $sql .= $dist ? "DISTINCT " : ''; - if (is_array($table) || is_string($table)) { - $this->sql .= " FROM {$this->prepareAliases($table)}"; - } else { - $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array'); - } + if (is_string($table) && $this->searchForSpecChars($table) && $fields == '*') { + $sql .= $preparedTable; + $this->fields = $this->prepareAliases($table); + } else { + $this->fields = $fields; + $sql .= "{$preparedFields} FROM {$preparedTable}"; + } + + if ($this->concat) { + $this->sql .= $sql; + } else { + $this->sql = $sql; + } return $this; } @@ -608,9 +656,9 @@ public function like($field, string $value = ''): QueryBuilder return $this; } - if (is_string($field) && !empty($field) && is_string($value) && !empty($value)) { - $this->where([[$field, 'LIKE', $value]]); - } else if (is_string($field) && empty($value)) { + if (is_string($field) && !empty($field) && !empty($value)) { + $this->where([[$field, 'LIKE', $value]]); + } else if (is_string($field) && empty($value)) { $this->where($field); } else if (is_array($field)) { $this->where([[$field[0], 'LIKE', $field[1]]]); @@ -646,7 +694,8 @@ public function notLike($field, string $value = ''): QueryBuilder * @param string $field * @return $this */ - public function isNull(string $field) { + public function isNull(string $field): QueryBuilder + { if (empty($field)) { $this->setError('Empty $field in ' . __METHOD__); return $this; @@ -660,7 +709,8 @@ public function isNull(string $field) { * @param string $field * @return $this */ - public function isNotNull(string $field) { + public function isNotNull(string $field): QueryBuilder + { if (empty($field)) { $this->setError('Empty $field in ' . __METHOD__); return $this; @@ -674,7 +724,8 @@ public function isNotNull(string $field) { * @param string $field * @return $this */ - public function notNull(string $field) { + public function notNull(string $field): QueryBuilder + { $this->isNotNull($field); return $this; } @@ -685,7 +736,12 @@ public function notNull(string $field) { */ public function limit(int $limit = 1): QueryBuilder { - $this->sql .= " LIMIT {$limit}"; + if (mb_strpos(mb_strtolower($this->sql), 'delete') !== false && $this->getDriver() == 'sqlite') { + return $this; + } + + $this->sql .= " LIMIT {$limit}"; + return $this; } @@ -762,8 +818,8 @@ public function delete($table): QueryBuilder return $this; } - if (is_array($table) or is_string($table)) { - $table = $this->prepareAliases($table); + if (is_array($table) || is_string($table)) { + $table = $this->prepareTables($table); } else { $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array.'); return $this; @@ -789,13 +845,14 @@ public function insert($table, array $fields = []): QueryBuilder } if (is_array($table) || is_string($table)) { - $table = $this->prepareAliases($table); + $table = $this->prepareTables($table); } else { $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array.'); return $this; } $this->reset(); + $this->fields = $fields; if (isset($fields[0]) && is_array($fields[0])) { $names = array_shift($fields); @@ -832,6 +889,8 @@ public function update($table, array $fields = []): QueryBuilder return $this; } + $this->fields = $fields; + if (is_array($table) or is_string($table)) { $table = $this->prepareAliases($table); } else { @@ -863,18 +922,26 @@ public function join($table, $on, string $join_type = 'INNER'): QueryBuilder { $join_type = strtoupper($join_type); - if (empty($join_type) || !in_array($join_type, self::JOIN_TYPES)) { - $this->setError('Empty $join_type or is not allowed in ' . __METHOD__); - return $this; - } + if (empty($table)) { + $this->setError('Empty $table in ' . __METHOD__); + return $this; + } - if (empty($table)) { - $this->setError('Empty $table in ' . __METHOD__); - return $this; - } + if (empty($join_type)) { + $this->setError('Empty $join_type in ' . __METHOD__); + return $this; + } + + if ($this->getDriver() == 'sqlite' && !in_array($join_type, self::SQLITE_JOIN_TYPES)) { + $this->setError('$join_type is not allowed in SQLite in ' . __METHOD__); + return $this; + } else if (!in_array($join_type, self::JOIN_TYPES)) { + $this->setError('$join_type is not allowed in ' . __METHOD__); + return $this; + } if (is_array($table) || is_string($table)) { - $this->sql .= " {$join_type} JOIN {$this->prepareAliases($table)}"; + $this->sql .= " {$join_type} JOIN {$this->prepareTables($table)}"; } else { $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array.'); return $this; @@ -891,11 +958,129 @@ public function join($table, $on, string $join_type = 'INNER'): QueryBuilder } } - $this->setError(); - return $this; } + /** + * @param bool $unionAll + * @return $this + */ + public function union(bool $unionAll = false): QueryBuilder + { + $this->concat = true; + $this->sql .= $unionAll ? ' UNION ALL ' : ' UNION '; + return $this; + } + + /** + * @param string|array $table + * @param bool $unionAll + * @return $this + */ + public function unionSelect($table, bool $unionAll = false): QueryBuilder + { + if (empty($table)) { + $this->setError('Empty $table in ' . __METHOD__); + return $this; + } + + if (mb_strpos(mb_strtolower($this->sql), 'union') !== false) { + $this->setError('SQL has already UNION in ' . __METHOD__); + return $this; + } + + $this->concat = true; + $fields = $this->fields ? : '*'; + $this->sql .= $unionAll ? ' UNION ALL ' : ' UNION '; + $this->sql .= "SELECT {$this->prepareAliases($fields)}"; + + if (is_array($table) || is_string($table)) { + $this->sql .= " FROM {$this->prepareTables($table)}"; + } else { + $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array'); + } + + return $this; + } + + /** + * @return $this + */ + public function excepts(): QueryBuilder + { + $this->concat = true; + $this->sql .= ' EXCEPT '; + return $this; + } + + /** + * @param $table + * @return $this + */ + public function exceptSelect($table): QueryBuilder + { + if (empty($table)) { + $this->setError('Empty $table in ' . __METHOD__); + return $this; + } + + if (mb_strpos(mb_strtolower($this->sql), 'except') !== false) { + $this->setError('SQL has already EXCEPT in ' . __METHOD__); + return $this; + } + + $this->concat = true; + $fields = $this->fields ? : '*'; + $this->sql .= " EXCEPT SELECT {$this->prepareAliases($fields)}"; + + if (is_array($table) || is_string($table)) { + $this->sql .= " FROM {$this->prepareTables($table)}"; + } else { + $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array'); + } + + return $this; + } + + /** + * @return $this + */ + public function intersect(): QueryBuilder + { + $this->concat = true; + $this->sql .= ' INTERSECT '; + return $this; + } + + /** + * @param $table + * @return $this + */ + public function intersectSelect($table): QueryBuilder + { + if (empty($table)) { + $this->setError('Empty $table in ' . __METHOD__); + return $this; + } + + if (mb_strpos(mb_strtolower($this->sql), 'intersect') !== false) { + $this->setError('SQL has already INTERSECT in ' . __METHOD__); + return $this; + } + + $this->concat = true; + $fields = $this->fields ? : '*'; + $this->sql .= " INTERSECT SELECT {$this->prepareAliases($fields)}"; + + if (is_array($table) || is_string($table)) { + $this->sql .= " FROM {$this->prepareTables($table)}"; + } else { + $this->setError('Incorrect type of $table in ' . __METHOD__ . '. $table must be a string or an array'); + } + + return $this; + } + /** * @return string */ @@ -904,7 +1089,12 @@ public function __toString(): string return $this->getSql(); } - public function createView(string $viewName, bool $addExists = true) + /** + * @param string $viewName + * @param bool $addExists + * @return $this + */ + public function createView(string $viewName, bool $addExists = true): QueryBuilder { // this method will be moved to another class if (empty($viewName)) { @@ -924,7 +1114,12 @@ public function createView(string $viewName, bool $addExists = true) return $this; } - public function dropView(string $viewName, bool $addExists = true) + /** + * @param string $viewName + * @param bool $addExists + * @return $this + */ + public function dropView(string $viewName, bool $addExists = true): QueryBuilder { // this method will be moved to another class if (empty($viewName)) { @@ -942,17 +1137,17 @@ public function dropView(string $viewName, bool $addExists = true) /** * @param string $table - * @param bool $add_exists + * @param bool $addExists * @return $this */ - public function drop(string $table, bool $add_exists = true): QueryBuilder + public function drop(string $table, bool $addExists = true): QueryBuilder { if (empty($table)) { $this->setError('Empty $table in ' . __METHOD__); return $this; } - $exists = ($add_exists) ? 'IF EXISTS ' : ''; + $exists = ($addExists) ? 'IF EXISTS ' : ''; $this->reset(); $this->sql = "DROP TABLE {$exists}`{$table}`"; @@ -976,4 +1171,9 @@ public function truncate(string $table): QueryBuilder return $this; } + + public function getDriver(): string + { + return strtolower($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME)); + } } diff --git a/tests/SqlDeleteTest.php b/tests/SqlDeleteTest.php new file mode 100644 index 0000000..bf9abd6 --- /dev/null +++ b/tests/SqlDeleteTest.php @@ -0,0 +1,98 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testDeleteEmptyTable() + { + $result = $this->qb->delete(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table in QueryBuilder::delete'); + } + + public function testDeleteTableAsString() + { + $result = $this->qb->delete('groups'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DELETE FROM `groups`", $result->getSql()); + } + + public function testDeleteTableAsArray() + { + $result = $this->qb->delete(['g' => 'groups']); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DELETE FROM `groups` AS `g`", $result->getSql()); + } + + public function testDeleteEq() + { + $result = $this->qb->delete('comments')->where([['user_id', '=', 10]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DELETE FROM `comments` WHERE (`user_id` = 10)", $result->getSql()); + $this->assertSame([10], $result->getParams()); + } + + public function testDeleteNoEq() + { + $result = $this->qb->delete('comments')->where([['user_id', 10]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DELETE FROM `comments` WHERE (`user_id` = 10)", $result->getSql()); + $this->assertSame([10], $result->getParams()); + } + + public function testDeleteLimitEq() + { + $result = $this->qb->delete('users')->where([['name', '=', 'John']])->limit(); + + $this->assertSame(false, $result->hasError()); + if ($this->qb->getDriver() == 'sqlite') { + $this->assertSame("DELETE FROM `users` WHERE (`name` = 'John')", $result->getSql()); + } else { + $this->assertSame("DELETE FROM `users` WHERE (`name` = 'John') LIMIT 1", $result->getSql()); + } + $this->assertSame(['John'], $result->getParams()); + } + + public function testDeleteLimitNoEq() + { + $result = $this->qb->delete('users')->where([['name', 'John']])->limit(); + + $this->assertSame(false, $result->hasError()); + if ($this->qb->getDriver() == 'sqlite') { + $this->assertSame("DELETE FROM `users` WHERE (`name` = 'John')", $result->getSql()); + } else { + $this->assertSame("DELETE FROM `users` WHERE (`name` = 'John') LIMIT 1", $result->getSql()); + } + $this->assertSame(['John'], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +} diff --git a/tests/SqlInsertTest.php b/tests/SqlInsertTest.php new file mode 100644 index 0000000..aef15df --- /dev/null +++ b/tests/SqlInsertTest.php @@ -0,0 +1,106 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testInsertEmptyTable() + { + $result = $this->qb->insert('', ['param' => 'new_value']); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::insert'); + } + + public function testInsertEmptyFields() + { + $result = $this->qb->insert('params', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $this->qb->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::insert'); + } + + public function testInsertEmptyTableAndFields() + { + $result = $this->qb->insert('', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::insert'); + } + + public function testInsertTableAsString() + { + $result = $this->qb->insert('groups', [ + 'name' => 'Moderator', + 'permissions' => 'moderator' + ]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("INSERT INTO `groups` (`name`, `permissions`) VALUES ('Moderator','moderator')", $result->getSql()); + $this->assertSame(['Moderator', 'moderator'], $result->getParams()); + } + + public function testInsertTableAsArray() + { + $result = $this->qb->insert(['g' => 'groups'], [ + 'name' => 'Moderator', + 'permissions' => 'moderator' + ]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("INSERT INTO `groups` AS `g` (`name`, `permissions`) VALUES ('Moderator','moderator')", $result->getSql()); + $this->assertSame(['Moderator', 'moderator'], $result->getParams()); + } + + public function testInsertMultipleTableAsString() + { + $result = $this->qb->insert('groups', [ + ['name', 'role'], + ['Moderator', 'moderator'], ['Moderator2', 'moderator'], + ['User', 'user'], ['User2', 'user'] + ]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("INSERT INTO `groups` (`name`, `role`) VALUES ('Moderator','moderator'),('Moderator2','moderator'),('User','user'),('User2','user')", $result->getSql()); + $this->assertSame(['Moderator', 'moderator', 'Moderator2', 'moderator', 'User', 'user', 'User2', 'user'], $result->getParams()); + } + + public function testInsertMultipleTableAsArray() + { + $result = $this->qb->insert(['g' => 'groups'], [ + ['name', 'role'], + ['Moderator', 'moderator'], ['Moderator2', 'moderator'], + ['User', 'user'], ['User2', 'user'] + ]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("INSERT INTO `groups` AS `g` (`name`, `role`) VALUES ('Moderator','moderator'),('Moderator2','moderator'),('User','user'),('User2','user')", $result->getSql()); + $this->assertSame(['Moderator', 'moderator', 'Moderator2', 'moderator', 'User', 'user', 'User2', 'user'], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +} diff --git a/tests/SqlSelectExtTest.php b/tests/SqlSelectExtTest.php new file mode 100644 index 0000000..7a5f9db --- /dev/null +++ b/tests/SqlSelectExtTest.php @@ -0,0 +1,407 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testSelectInnerJoinEmptyTable() + { + $result = $this->qb->select('users')->join('', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $table in QueryBuilder::join'); + } + + public function testSelectInnerJoinIncorrectTable() + { + $result = $this->qb->select('users')->join(2, []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Incorrect type of $table in QueryBuilder::join. $table must be a string or an array.'); + } + + public function testSelectInnerJoinEmptyJoinType() + { + $result = $this->qb->select('users')->join('clients', [], ''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $join_type in QueryBuilder::join'); + } + + public function testSelectInnerJoinIncorrectJoinType() + { + $result = $this->qb->select('users')->join('clients', [], 'asdasd'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + if ($this->qb->getDriver() == 'sqlite') { + $this->assertSame($result->getErrorMessage(), '$join_type is not allowed in SQLite in QueryBuilder::join'); + } else { + $this->assertSame($result->getErrorMessage(), '$join_type is not allowed in QueryBuilder::join'); + } + } + + public function testSelectInnerJoinIncorrectSqliteJoinType() + { + if ($this->qb->getDriver() == 'sqlite') { + $result = $this->qb->select('users')->join('clients', [], 'full'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), '$join_type is not allowed in SQLite in QueryBuilder::join'); + } + } + + public function testSelectInnerJoinIncorrectOnType() + { + $result = $this->qb->select('users')->join('clients', 2); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Incorrect type of $on in QueryBuilder::join. $on must be a string or an array.'); + } + + public function testSelectInnerJoinArray() + { + $result = $this->qb->select(['u' => 'users'], ['u.id', 'u.email', 'u.username', 'perms' => 'groups.permissions']) + ->join('groups', ['u.group_id', 'groups.id']); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `u`.`id`, `u`.`email`, `u`.`username`, `groups`.`permissions` AS `perms` FROM `users` AS `u` INNER JOIN `groups` ON `u`.`group_id` = `groups`.`id`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectInnerJoin3StrWhere() + { + $result = $this->qb->select(['cp' => 'cabs_printers'], ['cp.id', 'cp.cab_id', 'cab_name' => 'cb.name', + 'cp.printer_id', 'printer_name' => 'p.name', 'cartridge_type' => 'c.name', 'cp.comment']) + ->join(['cb' => 'cabs'], ['cp.cab_id', 'cb.id']) + ->join(['p' => 'printer_models'], ['cp.printer_id', 'p.id']) + ->join(['c' => 'cartridge_types'], 'p.cartridge_id=c.id') + ->where([['cp.cab_id', 'in', [11, 12, 13]], 'or', ['cp.cab_id', 5], 'and', ['p.id', '>', 'c.id']]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `cp`.`id`, `cp`.`cab_id`, `cb`.`name` AS `cab_name`, `cp`.`printer_id`, `p`.`name` AS `printer_name`, `c`.`name` AS `cartridge_type`, `cp`.`comment` FROM `cabs_printers` AS `cp` INNER JOIN `cabs` AS `cb` ON `cp`.`cab_id` = `cb`.`id` INNER JOIN `printer_models` AS `p` ON `cp`.`printer_id` = `p`.`id` INNER JOIN `cartridge_types` AS `c` ON p.cartridge_id=c.id WHERE (`cp`.`cab_id` IN (11,12,13)) OR (`cp`.`cab_id` = 5) AND (`p`.`id` > c.id)", $result->getSql()); + $this->assertSame([11, 12, 13, 5], $result->getParams()); + } + + public function testSelectInnerJoin3GroupByOrderBy() + { + $result = $this->qb->select(['cp' => 'cabs_printers'], ['cp.id', 'cp.cab_id', 'cab_name' => 'cb.name', 'cp.printer_id', + 'cartridge_id' => 'c.id', 'printer_name' => 'p.name', 'cartridge_type' => 'c.name', 'cp.comment']) + ->join(['cb' => 'cabs'], ['cp.cab_id', 'cb.id']) + ->join(['p' => 'printer_models'], ['cp.printer_id', 'p.id']) + ->join(['c' => 'cartridge_types'], ['p.cartridge_id', 'c.id']) + ->groupBy(['cp.printer_id', 'cartridge_id']) + ->orderBy(['cp.cab_id', 'cp.printer_id desc']); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `cp`.`id`, `cp`.`cab_id`, `cb`.`name` AS `cab_name`, `cp`.`printer_id`, `c`.`id` AS `cartridge_id`, `p`.`name` AS `printer_name`, `c`.`name` AS `cartridge_type`, `cp`.`comment` FROM `cabs_printers` AS `cp` INNER JOIN `cabs` AS `cb` ON `cp`.`cab_id` = `cb`.`id` INNER JOIN `printer_models` AS `p` ON `cp`.`printer_id` = `p`.`id` INNER JOIN `cartridge_types` AS `c` ON `p`.`cartridge_id` = `c`.`id` GROUP BY `cp`.`printer_id`, `cartridge_id` ORDER BY `cp`.`cab_id` ASC, `cp`.`printer_id` DESC", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectLeftJoin() + { + $result = $this->qb->select('employees', ['employees.employee_id', 'employees.last_name', 'positions.title']) + ->join('positions', ['employees.position_id', 'positions.position_id'], 'left'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `employees`.`employee_id`, `employees`.`last_name`, `positions`.`title` FROM `employees` LEFT JOIN `positions` ON `employees`.`position_id` = `positions`.`position_id`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectLeftOuterJoin() + { + $result = $this->qb->select(['e' => 'employees'], ['e.employee_id', 'e.last_name', 'p.title']) + ->join(['p' => 'positions'], ['e.position_id', 'p.position_id'], 'left outer'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `e`.`employee_id`, `e`.`last_name`, `p`.`title` FROM `employees` AS `e` LEFT OUTER JOIN `positions` AS `p` ON `e`.`position_id` = `p`.`position_id`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectCrossJoin() + { + $result = $this->qb->select('positions')->join('departments', [], 'cross'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `positions` CROSS JOIN `departments`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectUnionWhere() + { + $result = $this->qb->select('clients', ['name', 'age', 'total_sum' => 'account_sum + account_sum * 0.1']) + ->where([['account_sum', '<', 3000]]) + ->union() + ->select('clients', ['name', 'age', 'total_sum' => 'account_sum + account_sum * 0.3']) + ->where([['account_sum', '>=', 3000]]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `name`, `age`, account_sum + account_sum * 0.1 AS `total_sum` FROM `clients` WHERE (`account_sum` < 3000) UNION SELECT `name`, `age`, account_sum + account_sum * 0.3 AS `total_sum` FROM `clients` WHERE (`account_sum` >= 3000)", $result->getSql()); + $this->assertSame([3000, 3000], $result->getParams()); + } + + public function testSelectUnionAllWhere() + { + $result = $this->qb->select('clients', ['name', 'age', 'total_sum' => 'account_sum + account_sum * 0.1']) + ->where([['account_sum', '<', 3000]]) + ->union(true) + ->select('clients', ['name', 'age', 'total_sum' => 'account_sum + account_sum * 0.3']) + ->where([['account_sum', '>=', 3000]]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `name`, `age`, account_sum + account_sum * 0.1 AS `total_sum` FROM `clients` WHERE (`account_sum` < 3000) UNION ALL SELECT `name`, `age`, account_sum + account_sum * 0.3 AS `total_sum` FROM `clients` WHERE (`account_sum` >= 3000)", $result->getSql()); + $this->assertSame([3000, 3000], $result->getParams()); + } + + public function testSelectUnionWhereOrderBy() + { + $result = $this->qb->select('departments', ['department_id', 'department_name']) + ->where([['department_id', 'in', [1, 2]]]) + ->union() + ->select('employees', ['employee_id', 'last_name']) + ->where([['hire_date', '2024-02-08']])->orderBy('2'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department_id`, `department_name` FROM `departments` WHERE (`department_id` IN (1,2)) UNION SELECT `employee_id`, `last_name` FROM `employees` WHERE (`hire_date` = '2024-02-08') ORDER BY `2` ASC", $result->getSql()); + $this->assertSame([1, 2, '2024-02-08'], $result->getParams()); + } + + public function testSelectUnionAllWhereOrderBy() + { + $result = $this->qb->select('departments', ['department_id', 'department_name']) + ->where([['department_id', '>=', 10]]) + ->union(true) + ->select('employees', ['employee_id', 'last_name']) + ->where([['last_name', 'Rassohin']])->orderBy('2'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department_id`, `department_name` FROM `departments` WHERE (`department_id` >= 10) UNION ALL SELECT `employee_id`, `last_name` FROM `employees` WHERE (`last_name` = 'Rassohin') ORDER BY `2` ASC", $result->getSql()); + $this->assertSame([10, 'Rassohin'], $result->getParams()); + } + + public function testUnionSelectEmptyTable() + { + $result = $this->qb->select('clients', ['name', 'age'])->unionSelect(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $table in QueryBuilder::unionSelect'); + } + + public function testUnionSelectIncorrectTable() + { + $result = $this->qb->select('clients', ['name', 'age'])->unionSelect(2); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Incorrect type of $table in QueryBuilder::unionSelect. $table must be a string or an array'); + } + + public function testUnionSelectDoubleUnion() + { + $result = $this->qb->select('clients', ['name', 'age'])->union()->unionSelect('clients'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'SQL has already UNION in QueryBuilder::unionSelect'); + } + + public function testUnionSelectWhere() + { + $result = $this->qb->select('clients', ['name', 'age'])->where([['id', '<', 10]]) + ->unionSelect('employees')->where([['id', 1]]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `name`, `age` FROM `clients` WHERE (`id` < 10) UNION SELECT `name`, `age` FROM `employees` WHERE (`id` = 1)", $result->getSql()); + $this->assertSame([10, 1], $result->getParams()); + } + + public function testUnionAllSelectWhere() + { + $result = $this->qb->select('cabs', ['id', 'name']) + ->unionSelect('printer_models', true)->where([['id', '<', 10]]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `id`, `name` FROM `cabs` UNION ALL SELECT `id`, `name` FROM `printer_models` WHERE (`id` < 10)", $result->getSql()); + $this->assertSame([10], $result->getParams()); + } + + public function testSelectExceptsWhere() + { + $result = $this->qb->select('contacts', ['contact_id', 'last_name', 'first_name']) + ->where([['contact_id', '>=', 74]]) + ->excepts() + ->select('employees', ['employee_id', 'last_name', 'first_name']) + ->where([['first_name', 'Sandra']]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `contact_id`, `last_name`, `first_name` FROM `contacts` WHERE (`contact_id` >= 74) EXCEPT SELECT `employee_id`, `last_name`, `first_name` FROM `employees` WHERE (`first_name` = 'Sandra')", $result->getSql()); + $this->assertSame([74, 'Sandra'], $result->getParams()); + } + + public function testSelectExceptsWhereOrderBy() + { + $result = $this->qb->select('suppliers', ['supplier_id', 'state'])->where([['state', 'Nevada']]) + ->excepts() + ->select('companies', ['company_id', 'state'])->where([['company_id', '<', 2000]])->orderBy('1 desc'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `supplier_id`, `state` FROM `suppliers` WHERE (`state` = 'Nevada') EXCEPT SELECT `company_id`, `state` FROM `companies` WHERE (`company_id` < 2000) ORDER BY `1` DESC", $result->getSql()); + $this->assertSame(['Nevada', 2000], $result->getParams()); + } + + public function testExpectSelectEmptyTable() + { + $result = $this->qb->select('clients', ['name', 'age'])->exceptSelect(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $table in QueryBuilder::exceptSelect'); + } + + public function testExpectSelectIncorrectTable() + { + $result = $this->qb->select('clients', ['name', 'age'])->exceptSelect(2); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Incorrect type of $table in QueryBuilder::exceptSelect. $table must be a string or an array'); + } + + public function testExpectSelectDoubleExpect() + { + $result = $this->qb->select('clients', ['name', 'age'])->excepts()->exceptSelect('clients'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'SQL has already EXCEPT in QueryBuilder::exceptSelect'); + } + + public function testExceptSelect() + { + $result = $this->qb->select('departments', 'department_id')->exceptSelect('employees'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department_id` FROM `departments` EXCEPT SELECT `department_id` FROM `employees`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectIntersectWhere() + { + $result = $this->qb->select('departments', 'department_id')->where([['department_id', '>=', 25]]) + ->intersect() + ->select('employees', 'department_id')->where([['last_name', '<>', 'Petrov']]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department_id` FROM `departments` WHERE (`department_id` >= 25) INTERSECT SELECT `department_id` FROM `employees` WHERE (`last_name` <> 'Petrov')", $result->getSql()); + $this->assertSame([25, 'Petrov'], $result->getParams()); + } + + public function testSelectIntersectWhere2() + { + $result = $this->qb->select('contacts', ['contact_id', 'last_name', 'first_name']) + ->where([['contact_id', '>', 50]]) + ->intersect() + ->select('customers', ['customer_id', 'last_name', 'first_name']) + ->where([['last_name', '<>', 'Zagoskin']]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `contact_id`, `last_name`, `first_name` FROM `contacts` WHERE (`contact_id` > 50) INTERSECT SELECT `customer_id`, `last_name`, `first_name` FROM `customers` WHERE (`last_name` <> 'Zagoskin')", $result->getSql()); + $this->assertSame([50, 'Zagoskin'], $result->getParams()); + } + + public function testSelectIntersectWhereOrderBy() + { + $result = $this->qb->select('departments', ['department_id', 'state']) + ->where([['department_id', '>=', 25]]) + ->intersect() + ->select('companies', ['company_id', 'state']) + ->like('company_name', 'G%')->orderBy('1'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department_id`, `state` FROM `departments` WHERE (`department_id` >= 25) INTERSECT SELECT `company_id`, `state` FROM `companies` WHERE (`company_name` LIKE 'G%') ORDER BY `1` ASC", $result->getSql()); + $this->assertSame([25, 'G%'], $result->getParams()); + } + + public function testIntersectSelectEmptyTable() + { + $result = $this->qb->select('clients', ['name', 'age'])->intersectSelect(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $table in QueryBuilder::intersectSelect'); + } + + public function testIntersectSelectIncorrectTable() + { + $result = $this->qb->select('clients', ['name', 'age'])->intersectSelect(2); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Incorrect type of $table in QueryBuilder::intersectSelect. $table must be a string or an array'); + } + + public function testIntersectSelectDoubleExpect() + { + $result = $this->qb->select('clients', ['name', 'age'])->intersect()->intersectSelect('clients'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'SQL has already INTERSECT in QueryBuilder::intersectSelect'); + } + + public function testIntersectSelect() + { + $result = $this->qb->select('departments', 'department_id')->intersectSelect('employees'); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department_id` FROM `departments` INTERSECT SELECT `department_id` FROM `employees`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +} diff --git a/tests/SqlSelectTest.php b/tests/SqlSelectTest.php new file mode 100644 index 0000000..a72f498 --- /dev/null +++ b/tests/SqlSelectTest.php @@ -0,0 +1,408 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testEmptyTable() + { + $result = $this->qb->select('', 'param'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::select'); + } + + public function testEmptyFields() + { + $result = $this->qb->select('users', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($result->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::select'); + } + + public function testEmptyTableAndFields() + { + $result = $this->qb->select('', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::select'); + } + + public function testGetSql() + { + $result = $this->qb->select('users')->where([['id', 1]]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` = 1)", $result->getSql()); + $this->assertSame([1], $result->getParams()); + } + + public function testGetSqlNoValues() + { + $result = $this->qb->select('users')->where([['id', 1]]); + + $this->assertSame($this->qb, $result); + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` = ?)", $result->getSql(false)); + $this->assertSame([1], $result->getParams()); + } + + public function testSelectAll() + { + $result = $this->qb->select('users'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectWhereEq() + { + $result = $this->qb->select('users')->where([['id', '=', 10]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` = 10)", $result->getSql()); + $this->assertSame([10], $result->getParams()); + } + + public function testSelectWhereNoEq() + { + $result = $this->qb->select('users')->where([['id', 10]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` = 10)", $result->getSql()); + $this->assertSame([10], $result->getParams()); + } + + public function testSelectWhereAndEq() + { + $result = $this->qb->select('users')->where([['id', '>', 1], 'and', ['group_id', '=', 2]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` > 1) AND (`group_id` = 2)", $result->getSql()); + $this->assertSame([1, 2], $result->getParams()); + } + + public function testSelectWhereAndNoEq() + { + $result = $this->qb->select('users')->where([['id', '>', 1], 'and', ['group_id', 2]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` > 1) AND (`group_id` = 2)", $result->getSql()); + $this->assertSame([1, 2], $result->getParams()); + } + + public function testSelectWhereOrEq() + { + $result = $this->qb->select('users')->where([['id', '>', 1], 'or', ['group_id', '=', 2]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` > 1) OR (`group_id` = 2)", $result->getSql()); + $this->assertSame([1, 2], $result->getParams()); + } + + public function testSelectWhereOrNoEq() + { + $result = $this->qb->select('users')->where([['id', '>', 1], 'or', ['group_id', 2]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`id` > 1) OR (`group_id` = 2)", $result->getSql()); + $this->assertSame([1, 2], $result->getParams()); + } + + public function testSelectWhereLike() + { + $result = $this->qb->select('users')->where([['name', 'LIKE', '%John%']]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`name` LIKE '%John%')", $result->getSql()); + $this->assertSame(['%John%'], $result->getParams()); + } + + public function testSelectLikeArray() + { + $result = $this->qb->select('users')->like(['name', '%John%']); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`name` LIKE '%John%')", $result->getSql()); + $this->assertSame(['%John%'], $result->getParams()); + } + + public function testSelectLikeStr() + { + $result = $this->qb->select('users')->like('name', '%John%'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`name` LIKE '%John%')", $result->getSql()); + $this->assertSame(['%John%'], $result->getParams()); + } + + public function testSelectWhereNotLike() + { + $result = $this->qb->select('users')->where([['name', 'NOT LIKE', '%John%']]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`name` NOT LIKE '%John%')", $result->getSql()); + $this->assertSame(['%John%'], $result->getParams()); + } + + public function testSelectNotLikeArray() + { + $result = $this->qb->select('users')->notLike(['name', '%John%']); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`name` NOT LIKE '%John%')", $result->getSql()); + $this->assertSame(['%John%'], $result->getParams()); + } + + public function testSelectNotLikeStr() + { + $result = $this->qb->select('users')->notLike('name', '%John%'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`name` NOT LIKE '%John%')", $result->getSql()); + $this->assertSame(['%John%'], $result->getParams()); + } + + public function testSelectWhereIsNull() + { + $result = $this->qb->select('users')->where([['phone', 'is null']]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`phone` IS NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectIsNull() + { + $result = $this->qb->select('users')->isNull('phone'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `users` WHERE (`phone` IS NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectWhereIsNotNull() + { + $result = $this->qb->select('customers')->where([['address', 'is not null']]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `customers` WHERE (`address` IS NOT NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectIsNotNull() + { + $result = $this->qb->select('customers')->isNotNull('address'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `customers` WHERE (`address` IS NOT NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectNotNull() + { + $result = $this->qb->select('customers')->notNull('address'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `customers` WHERE (`address` IS NOT NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectOffset() + { + $result = $this->qb->select('posts')->where([['user_id', 3]])->offset(14); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `posts` WHERE (`user_id` = 3) OFFSET 14", $result->getSql()); + $this->assertSame([3], $result->getParams()); + } + + public function testSelectLimit() + { + $result = $this->qb->select('posts')->where([['id', '>', 42]])->limit(7); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT * FROM `posts` WHERE (`id` > 42) LIMIT 7", $result->getSql()); + $this->assertSame([42], $result->getParams()); + } + + public function testSelectCounter() + { + $result = $this->qb->select('users', ['counter' => 'COUNT(*)']); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT COUNT(*) AS `counter` FROM `users`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectDistinctOrderBy() + { + $result = $this->qb->select('customers', 'city', true)->orderBy('city'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT DISTINCT `city` FROM `customers` ORDER BY `city` ASC", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectDistinctOrderBy2Col() + { + $result = $this->qb->select('customers', ['city', 'country'], true)->orderBy('country desc'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT DISTINCT `city`, `country` FROM `customers` ORDER BY `country` DESC", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectOrderByTwoParams() + { + $result = $this->qb->select(['b' => 'branches'], ['b.id', 'b.name']) + ->where([['b.id', '>', 1], 'and', ['b.parent_id', 1]]) + ->orderBy('b.id', 'desc'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `b`.`id`, `b`.`name` FROM `branches` AS `b` WHERE (`b`.`id` > 1) AND (`b`.`parent_id` = 1) ORDER BY `b`.`id` DESC", $result->getSql()); + $this->assertSame([1, 1], $result->getParams()); + } + + public function testSelectOrderByOneParam() + { + $result = $this->qb->select(['b' => 'branches'], ['b.id', 'b.name']) + ->where([['b.id', '>', 1], 'and', ['b.parent_id', 1]]) + ->orderBy('b.id desc'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `b`.`id`, `b`.`name` FROM `branches` AS `b` WHERE (`b`.`id` > 1) AND (`b`.`parent_id` = 1) ORDER BY `b`.`id` DESC", $result->getSql()); + $this->assertSame([1, 1], $result->getParams()); + } + + public function testSelectGroupBy() + { + $result = $this->qb->select('posts', ['id', 'category', 'title']) + ->where([['views', '>=', 1000]])->groupBy('category'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `id`, `category`, `title` FROM `posts` WHERE (`views` >= 1000) GROUP BY `category`", $result->getSql()); + $this->assertSame([1000], $result->getParams()); + } + + public function testSelectGroupByHavingEq() + { + $result = $this->qb->select('orders', ['month_num' => 'MONTH(`created_at`)', 'total' => 'SUM(`total`)']) + ->where([['YEAR(`created_at`)', 2020]])->groupBy('month_num')->having([['total', '=', 20000]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT MONTH(`created_at`) AS `month_num`, SUM(`total`) AS `total` FROM `orders` WHERE (YEAR(`created_at`) = 2020) GROUP BY `month_num` HAVING (`total` = 20000)", $result->getSql()); + $this->assertSame([2020, 20000], $result->getParams()); + } + + public function testSelectGroupByHavingNoEqSum() + { + $result = $this->qb->select('orders', ['month_num' => 'MONTH(`created_at`)', 'total' => 'SUM(`total`)']) + ->where([['YEAR(`created_at`)', 2020]])->groupBy('month_num')->having([['total', 20000]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT MONTH(`created_at`) AS `month_num`, SUM(`total`) AS `total` FROM `orders` WHERE (YEAR(`created_at`) = 2020) GROUP BY `month_num` HAVING (`total` = 20000)", $result->getSql()); + $this->assertSame([2020, 20000], $result->getParams()); + } + + public function testSelectGroupByHavingMax() + { + $result = $this->qb->select('employees', ['department', 'Highest salary' => 'MAX(`salary`)']) + ->where([['favorite_website', 'Google.com']])->groupBy('department')->having([['MAX(`salary`)', '>=', 30000]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department`, MAX(`salary`) AS `Highest salary` FROM `employees` WHERE (`favorite_website` = 'Google.com') GROUP BY `department` HAVING (MAX(`salary`) >= 30000)", $result->getSql()); + $this->assertSame(['Google.com', 30000], $result->getParams()); + } + + public function testSelectGroupByHavingCount() + { + $result = $this->qb->select('employees', ['department', 'Number of employees' => 'COUNT(*)']) + ->where([['state', 'Nevada']])->groupBy('department')->having([['COUNT(*)', '>', 20]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT `department`, COUNT(*) AS `Number of employees` FROM `employees` WHERE (`state` = 'Nevada') GROUP BY `department` HAVING (COUNT(*) > 20)", $result->getSql()); + $this->assertSame(['Nevada', 20], $result->getParams()); + } + + public function testSelectSumm() + { + $result = $this->qb->select("1+5 as 'res'"); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT 1+5 as 'res'", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectSub() + { + $result = $this->qb->select("10 - 3 as 'res'"); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT 10 - 3 as 'res'", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectSubStr() + { + $result = $this->qb->select("substr('Hello world!', 1, 5) as 'str'"); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT substr('Hello world!', 1, 5) as 'str'", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectConcatStr() + { + $result = $this->qb->select("'Hello' || ' world!' as 'str'"); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT 'Hello' || ' world!' as 'str'", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectSqliteVersion() + { + $result = $this->qb->select("sqlite_version() as ver"); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT sqlite_version() as ver", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testSelectTime() + { + $result = $this->qb->select("strftime('%Y-%m-%d %H:%M', 'now')"); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("SELECT strftime('%Y-%m-%d %H:%M', 'now')", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +} diff --git a/tests/SqlTableTest.php b/tests/SqlTableTest.php new file mode 100644 index 0000000..a72015c --- /dev/null +++ b/tests/SqlTableTest.php @@ -0,0 +1,74 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testDropTableEmptyName() + { + $result = $this->qb->drop(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table in QueryBuilder::drop'); + } + + public function testDropTableExists() + { + $result = $this->qb->drop('temporary'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DROP TABLE IF EXISTS `temporary`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testDropTableNoExists() + { + $result = $this->qb->drop('temporary', false); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DROP TABLE `temporary`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testTruncateTableEmptyName() + { + $result = $this->qb->truncate(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table in QueryBuilder::truncate'); + } + + public function testTruncateTable() + { + $result = $this->qb->truncate('users'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("TRUNCATE TABLE `users`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +} diff --git a/tests/SqlUpdateTest.php b/tests/SqlUpdateTest.php new file mode 100644 index 0000000..beb1ddc --- /dev/null +++ b/tests/SqlUpdateTest.php @@ -0,0 +1,112 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testUpdateEmptyTable() + { + $result = $this->qb->update('', ['param' => 'new_value']); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::update'); + } + + public function testUpdateEmptyFields() + { + $result = $this->qb->update('params', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $this->qb->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::update'); + } + + public function testUpdateEmptyTableAndFields() + { + $result = $this->qb->update('', []); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $table or $fields in QueryBuilder::update'); + } + + public function testUpdateTableAsString() + { + $result = $this->qb->update('posts', ['status' => 'published'])->where([['YEAR(`updated_at`)', '>', 2020]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("UPDATE `posts` SET `status` = 'published' WHERE (YEAR(`updated_at`) > 2020)", $result->getSql()); + $this->assertSame(['published', 2020], $result->getParams()); + } + + public function testUpdateTableAsArray() + { + $result = $this->qb->update(['p' => 'posts'], ['status' => 'published'])->where([['YEAR(`updated_at`)', '>', 2020]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("UPDATE `posts` AS `p` SET `status` = 'published' WHERE (YEAR(`updated_at`) > 2020)", $result->getSql()); + $this->assertSame(['published', 2020], $result->getParams()); + } + + public function testUpdateEq() + { + $result = $this->qb->update('posts', ['status' => 'draft'])->where([['user_id', '=', 10]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("UPDATE `posts` SET `status` = 'draft' WHERE (`user_id` = 10)", $result->getSql()); + $this->assertSame(['draft', 10], $result->getParams()); + } + + public function testUpdateNoEq() + { + $result = $this->qb->update('posts', ['status' => 'draft'])->where([['user_id', 10]]); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("UPDATE `posts` SET `status` = 'draft' WHERE (`user_id` = 10)", $result->getSql()); + $this->assertSame(['draft', 10], $result->getParams()); + } + + public function testUpdateLimitEq() + { + $result = $this->qb->update('users', ['username' => 'John Doe', 'status' => 'new status']) + ->where([['id', '=', 7]])->limit(); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("UPDATE `users` SET `username` = 'John Doe', `status` = 'new status' WHERE (`id` = 7) LIMIT 1", $result->getSql()); + $this->assertSame(['John Doe', 'new status', 7], $result->getParams()); + } + + public function testUpdateLimitNoEq() + { + $result = $this->qb->update('users', ['username' => 'John Doe', 'status' => 'new status']) + ->where([['id', 7]])->limit(); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("UPDATE `users` SET `username` = 'John Doe', `status` = 'new status' WHERE (`id` = 7) LIMIT 1", $result->getSql()); + $this->assertSame(['John Doe', 'new status', 7], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +} diff --git a/tests/SqlViewsTest.php b/tests/SqlViewsTest.php new file mode 100644 index 0000000..5fdc8ea --- /dev/null +++ b/tests/SqlViewsTest.php @@ -0,0 +1,103 @@ +qb) { + $this->qb = new QueryBuilder(Connection::make($config['database'])); + } + } + + public function testCreateViewEmptyViewName() + { + $result = $this->qb->createView(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $viewName in QueryBuilder::createView'); + } + + public function testCreateViewNoSelect() + { + $result = $this->qb->delete('comments')->where([['user_id', 10]]) + ->createView('users_no_email'); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'No SELECT found in QueryBuilder::createView'); + } + + public function testCreateView() + { + $result = $this->qb->select('users')->where([['email', 'is null'], 'or', ['email', '']]) + ->createView('users_no_email'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("CREATE VIEW IF NOT EXISTS `users_no_email` AS SELECT * FROM `users` WHERE (`email` IS NULL) OR (`email` = '')", $result->getSql()); + $this->assertSame([''], $result->getParams()); + } + + public function testCreateViewExists() + { + $result = $this->qb->select('users')->isNull('email')->createView('users_no_email'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("CREATE VIEW IF NOT EXISTS `users_no_email` AS SELECT * FROM `users` WHERE (`email` IS NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testCreateViewNoExists() + { + $result = $this->qb->select('users')->isNull('email')->createView('users_no_email', false); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("CREATE VIEW `users_no_email` AS SELECT * FROM `users` WHERE (`email` IS NULL)", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testDropViewEmptyViewName() + { + $result = $this->qb->dropView(''); + + $this->assertSame($this->qb, $result); + $this->assertSame(true, $result->hasError()); + $this->assertSame($this->qb->getErrorMessage(), 'Empty $viewName in QueryBuilder::dropView'); + } + + public function testDropViewNoExists() + { + $result = $this->qb->dropView('users_no_email', false); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DROP VIEW `users_no_email`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + public function testDropViewExists() + { + $result = $this->qb->dropView('users_no_email'); + + $this->assertSame(false, $result->hasError()); + $this->assertSame("DROP VIEW IF EXISTS `users_no_email`", $result->getSql()); + $this->assertSame([], $result->getParams()); + } + + protected function tearDown(): void + { + $this->qb = null; + } +}