diff --git a/README.md b/README.md
index 502ae1e..74d7f80 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,47 @@
# chillerlan/php-database
-A PHP 7.4+ SQL client and querybuilder for the most common databases.
+A PHP SQL client and querybuilder for the most common databases, namely: MySQL, PostgreSQL, SQLite3, Microsoft SQL Server (Transact) and Firebird.
[![PHP Version Support][php-badge]][php]
[![version][packagist-badge]][packagist]
[![license][license-badge]][license]
-[![Coverage][coverage-badge]][coverage]
-[![Scrunitizer][scrutinizer-badge]][scrutinizer]
-[![Packagist downloads][downloads-badge]][downloads]
[![Continuous Integration][gh-action-badge]][gh-action]
+[![Coverage][coverage-badge]][coverage]
+[![Packagist downloads][downloads-badge]][downloads]
-[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-database?logo=php&color=8892BF
+[php-badge]: https://img.shields.io/packagist/php-v/chillerlan/php-database?logo=php&color=8892BF&logoColor=fff
[php]: https://www.php.net/supported-versions.php
-[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-database.svg?logo=packagist
+[packagist-badge]: https://img.shields.io/packagist/v/chillerlan/php-database.svg?logo=packagist&logoColor=fff
[packagist]: https://packagist.org/packages/chillerlan/php-database
[license-badge]: https://img.shields.io/github/license/chillerlan/php-database.svg
[license]: https://github.com/chillerlan/php-database/blob/main/LICENSE
-[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-database.svg?logo=codecov
+[gh-action-badge]: https://img.shields.io/github/actions/workflow/status/chillerlan/php-database/ci.yml?branch=main&logo=github&logoColor=fff
+[gh-action]: https://github.com/chillerlan/php-database/actions/workflows/ci.yml?query=branch%3Amain
+[coverage-badge]: https://img.shields.io/codecov/c/github/chillerlan/php-database.svg?logo=codecov&logoColor=fff
[coverage]: https://codecov.io/github/chillerlan/php-database
-[scrutinizer-badge]: https://img.shields.io/scrutinizer/g/chillerlan/php-database.svg?logo=scrutinizer
-[scrutinizer]: https://scrutinizer-ci.com/g/chillerlan/php-database
-[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-database.svg?logo=packagist
+[downloads-badge]: https://img.shields.io/packagist/dt/chillerlan/php-database.svg?logo=packagist&logoColor=fff
[downloads]: https://packagist.org/packages/chillerlan/php-database/stats
-[gh-action-badge]: https://github.com/chillerlan/php-database/workflows/Continuous%20Integration/badge.svg
-[gh-action]: https://github.com/chillerlan/php-database/actions
# Documentation
## Requirements
-- PHP 7.4+
+- PHP 8.2+
- one of the supported databases, set up to work with PHP:
- - [MySQL](https://dev.mysql.com/doc/refman/5.6/en/) (5.5+) / [MariaDB](https://mariadb.com/kb/en/library/basic-sql-statements/)
- - [PostgreSQL](https://www.postgresql.org/docs/9.5/static/index.html) (9.5+)
- - [SQLite3](https://www.sqlite.org/lang.html)
- - [Firebird](https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25.html) (2.5+)
- - [Microsoft SQL Server](https://github.com/Microsoft/msphpsql) ([transact-sql](https://docs.microsoft.com/sql/t-sql/language-reference))
+ - [MySQL](https://dev.mysql.com/doc/refman/5.6/en/) (5.5+) / [MariaDB](https://mariadb.com/kb/en/library/basic-sql-statements/) via [ext-mysqli](https://www.php.net/manual/en/book.mysqli.php) or [ext-pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php)
+ - [PostgreSQL](https://www.postgresql.org/docs/9.5/static/index.html) (9.5+) via [ext-pgsql](https://www.php.net/manual/en/book.pgsql.php) or [ext-pdo_pgsql](https://www.php.net/manual/en/ref.pdo-pgsql.php)
+ - [SQLite3](https://www.sqlite.org/lang.html) via [ext-pdo_sqlite](https://www.php.net/manual/en/ref.pdo-sqlite.php)
+ - [Firebird](https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25.html) (2.5+) via [ext-pdo_firebird](https://www.php.net/manual/en/ref.pdo-firebird.php)
+ - [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/sql-server-downloads) ([transact-sql](https://docs.microsoft.com/sql/t-sql/language-reference)) via [ext-sqlsrv or ext-pdo_sqlsrv](https://github.com/Microsoft/msphpsql)
## Installation
**requires [composer](https://getcomposer.org)**
### *composer.json*
- (note: replace `dev-main` with a [version boundary](https://getcomposer.org/doc/articles/versions.md#summary))
+(note: replace `dev-main` with a [version boundary](https://getcomposer.org/doc/articles/versions.md#summary))
```json
{
"require": {
- "php": "^7.4 || ^8.0",
+ "php": "^8.2",
"chillerlan/php-database": "dev-main"
}
}
@@ -52,449 +49,3 @@ A PHP 7.4+ SQL client and querybuilder for the most common databases.
Profit!
-## Usage
-
-### Getting started
-Both, the `DriverInterface` and `QueryBuilder` can be instanced on their own.
-However, since the `QueryBuilder` requires an instance of `DriverInterface` it's recommended to just use `Database` which instances both and provides all of their methods.
-A `DriverInterface` requires a `DatabaseOptions` object and accepts a `Psr\SimpleCache\CacheInterface` and a `Psr\Log\LoggerInterface` as optional parameters, the `QueryBuilder` accepts a `LoggerInterface` as additional parameter.
-
-```php
-$options = new DatabaseOptions;
-$options->database = 'whatever';
-$options->username = 'user';
-$options->password = 'supersecretpassword';
-```
-which is equivalent to
-```php
-$options = new DatabaseOptions([
- 'database' => 'whatever',
- 'username' => 'user',
- 'password' => 'supersecretpassword',
-]);
-```
-now instance a driver with these options
-```php
-$mysql = new MySQLiDrv($options, $cache, $log);
-$mysql->connect();
-
-// a raw query using the driver directly
-$result = $mysql->raw('SELECT * FROM sometable');
-```
-via the querybuilder
-
-```php
-$querybuilder = new QueryBuilder($mysql, $log)
-
-$result = $querybuilder->select->from(['sometable'])->query();
-```
-recommended way via `Database`, which provides all methods of `DriverInterface` and `QueryBuilder`
-```php
-$options->driver = MySQLiDrv::class;
-
-$db = new Database($options);
-$db->connect();
-
-$result = $db->raw('SELECT * FROM sometable');
-// is equivalent to
-$result = $db->select->from(['sometable'])->query();
-```
-
-### Properties of `DatabaseOptions`
-
-property | type | default | allowed | description
--------- | ---- | ------- | ------- | -----------
-`$driver` | string | `null` | `DriverInterface` | database driver to use (FQCN)
-`$querybuilder` | string | `null` | `QueryBuilderInterface` | query builder to use (FQCN) [optional]
-`$host` | string | 'localhost' | |
-`$port` | int | `null` | |
-`$socket` | string | `null` | |
-`$database` | string | `null` | |
-`$username` | string | `null` | |
-`$password` | string | `null` | |
-`$use_ssl` | bool | `false` | | indicates whether the connection should use SSL or not
-`$ssl_key` | string | `null` | |
-`$ssl_cert` | string | `null` | |
-`$ssl_ca` | string | `null` | |
-`$ssl_capath` | string | `null` | |
-`$ssl_cipher` | string | `null` | |
-`$mysqli_timeout` | int | 3 | |
-`$mysql_charset` | string | 'utf8mb4' | | [How to support full Unicode in MySQL](https://mathiasbynens.be/notes/mysql-utf8mb4)
-`$pgsql_charset` | string | 'UTF8' | |
-`$odbc_driver` | string | `null` | |
-`$convert_encoding_src` | string | `null` | [supported encodings](http://php.net/manual/mbstring.supported-encodings.php) | `mb_convert_encoding()`, used in `Result`
-`$convert_encoding_dest` | string | 'UTF-8' | [supported encodings](http://php.net/manual/mbstring.supported-encodings.php) | `mb_convert_encoding()`, used in `Result`
-`$mssql_timeout` | int | 3 | |
-`$mssql_charset` | string | 'UTF-8' | |
-`$mssql_encrypt` | bool | `false` | |
-
-### Methods of `DriverInterface`
-
-method | return
------- | ------
-`__construct(DatabaseOptions $options, CacheInterface $cache = null)` | -
-`connect()` | `DriverInterface`
-`disconnect()` | `bool`
-`getDBResource()` | resource|object
-`getClientInfo()` | `string`
-`getServerInfo()` | `string`
-`escape($data)` | `string` (subject to change)
-`raw(string $sql, string $index = null, bool $assoc = true)` | Result|bool
-`rawCached(string $sql, string $index = null, bool $assoc = true, int $ttl = null)` | Result|bool
-`prepared(string $sql, array $values = [], string $index = null, bool $assoc = true)` | Result|bool
-`preparedCached(string $sql, array $values = [], string $index = null, bool $assoc = true, int $ttl = null)` | Result|bool
-`multi(string $sql, array $values)` | `bool` (subject to change)
-`multiCallback(string $sql, array $data, $callback)` | `bool` (subject to change)
-
-### Methods of `QueryBuilder`
-All methods of `QueryBuilder` are also accessible as properties via magic methods.
-The returned object is a `Statement` of `\chillerlan\Database\Query\*` interfaces.
-
-method | return
------- | ------
-`__construct(DriverInterface $db, LoggerInterface $logger = null)` | -
-`select()` | `Select`
-`insert()` | `Insert`
-`update()` | `Update`
-`delete()` | `Delete`
-`create()` | `Create`
-`alter()` | `Alter`
-`drop()` | `Drop`
-
-### Methods of `Database`
-in addition to `DriverInterface` and `QueryBuilderInterface` methods
-
-method | return
------- | ------
-`__construct(DatabaseOptions $options, CacheInterface $cache = null, LoggerInterface $logger = null)` | -
-`getDriver()` | `DriverInterface`
-`getQueryBuilderFQCN()` | QueryBuilderInterface|null
-
-## The `Statement` interface
-
-method | return | description
------- | ------ | -----------
-`sql()` | `string` | returns the SQL for the current statement
-`bindValues()` | `array` | returns the values for each '?' parameter in the SQL
-`query(string $index = null` | `Result` | Executes the current statement. `$index` is being used in "SELECT" statements to determine a column to index the `Result` by. `$values`.
-`multi(array $values = null)` | bool | Executes the current statement as multi query. `$values` needs to be a multi dimensional array with each row.
-`callback(array $values = null, $callback = null)` | bool | Executes the current statement. `$index` is being used in "SELECT" statements to determine a column to index the `Result` by. `$values` and `$callback` can be used to provide multiple values on multi row "INSERT" or "UPDATE" queries.
-
-
-### `Create`
-
-method | return
------- | ------
-`database(string $dbname = null)` | `CreateDatabase`
-`table(string $tablename = null)` | `CreateTable`
-
-#### `CreateDatabase`
-
-method | description
------- | -----------
-`ifNotExists()` |
-`name(string $dbname = null)` |
-`charset(string $collation)` |
-
-```php
-$conn->create
- ->database('test')
- ->ifNotExists()
- ->charset('utf8mb4_bin')
- ->query();
-```
-```mysql
-CREATE DATABASE IF NOT EXISTS `test` CHARACTER SET utf8mb4 COLLATE utf8mb4_bin
-```
-
-#### `CreateTable`
-
-method | description
------- | -----------
-`ifNotExists()` |
-`name(string $tablename = null)` |
-`charset(string $collation)` |
-`primaryKey(string $field)` |
-`field(string $name, string $type, $length = null, string $attribute = null, string $collation = null, bool $isNull = null, string $defaultType = null, $defaultValue = null, string $extra = null)` |
-`int(string $name, int $length = null, $defaultValue = null , bool $isNull = null, string $attribute = null)` | convenience shortcut for `field()`, also `tinyint(...)`
-`varchar(string $name, int $length, $defaultValue = null , bool $isNull = null)` |
-`decimal(string $name, string $length, $defaultValue = null , bool $isNull = null)` |
-`text(string $name, $defaultValue = null , bool $isNull = true)` | also `tinytext()`
-`enum(string $name, array $values, $defaultValue = null , bool $isNull = null)` | currently the only way to create an "ENUM" field
-
-```php
-$conn->create
- ->table('products')
- ->ifNotExists()
- ->int('id', 10, null, false, 'UNSIGNED AUTO_INCREMENT')
- ->tinytext('name', null, false)
- ->varchar('type', 20)
- ->decimal('price', '9,2', 0)
- ->decimal('weight', '8,3')
- ->int('added', 10, 0, null, 'UNSIGNED')
- ->primaryKey('id')
- ->query();
-```
-The generated Query will look something like this
-```mysql
--- mysql
-
-CREATE TABLE IF NOT EXISTS `products` (
- `id` INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
- `name` TINYTEXT NOT NULL,
- `type` VARCHAR(20),
- `price` DECIMAL(9,2) NOT NULL DEFAULT 0,
- `weight` DECIMAL(8,3),
- `added` INT(10) UNSIGNED NOT NULL DEFAULT 0,
- PRIMARY KEY (`id`)
-)
-```
-Note that additional constraints and attributes will be appended regardless of the Query dialect
-```postgresql
--- postgres: attributes UNSIGNED and AUTO_INCREMENT are invalid
-
-CREATE TABLE IF NOT EXISTS "products" (
- "id" INT NOT NULL UNSIGNED AUTO_INCREMENT,
- "name" VARCHAR(255) NOT NULL,
- "type" VARCHAR(20),
- "price" DECIMAL(9,2) NOT NULL DEFAULT '0',
- "weight" DECIMAL(8,3),
- "added" INT NOT NULL UNSIGNED DEFAULT '0',
- PRIMARY KEY ("id")
-)
-```
-
-
-### `Insert`
-
-method | description
------- | -----------
-`into(string $table)` | The table where to insert data
-`values(array $values)` | An array of values where each row represents a row to insert `[['column' => 'value', ...], ...]`
-
-```php
-$conn->insert
- ->into('products')
- ->values(['name' => 'product1', 'type' => 'a', 'price' => 3.99, 'weight' => 0.1, 'added' => time()])
- ->query();
-```
-```mysql
-INSERT INTO `products` (`name`, `type`, `price`, `weight`, `added`) VALUES (?,?,?,?,?)
-```
-
-
-```php
-$values = [
- ['name' => 'product2', 'type' => 'b', 'price' => 4.20, 'weight' => 2.35, 'added' => time()],
- ['name' => 'product3', 'type' => 'b', 'price' => 6.50, 'weight' => 1.725, 'added' => time()],
-];
-
-$conn->insert
- ->into('products')
- ->multi($values);
-```
-As an alternative, you can provide the values via a callback
-```php
-$values = [
- ['product4', 'c', 3.99, 0.1,],
- ['product5', 'a', 4.20, 2.35,],
- ['product6', 'b', 6.50, 1.725,],
-];
-
-$conn->insert
- ->into('products')
- ->values([['name' => '?', 'type' => '?', 'price' => '?', 'weight' => '?', 'added' => '?']])
- ->callback($values, function($row){
- return [
- $row[0],
- $row[1],
- floatval($row[2]),
- floatval($row[3]),
- time(),
- ];
- });
-```
-
-
-### `Select`
-
-method | description
------- | -----------
-`distinct()` | sets the "DISTINCT" statement (if the Query dialect supports it)
-`cols(array $expressions)` | An array of column expressions. If omitted, a `SELECT * ...` will be performed. Example: `['col', 'alias' => 'col', 'alias' => ['col', 'sql_function']]`
-`from(array $expressions)` | An array of table expressions. Example: `['table', 'alias' => 'table']`
-`groupBy(array $expressions)` | An array of expressions to group by.
-`where($val1, $val2, $operator = '=', $bind = true, $join = 'AND')` | Adds a "WHERE" clause, comparing `$val1` and `$val2` by `$operator`. `$bind` specifies whether the value should be bound to a '?' parameter (default) or not (no effect if `$val2` is a `Select` interface). If more than one "WHERE" statement exists, they will be joined by `$join`.
-`openBracket($join = null)` | puts an opening bracket `(` at the current position in the "WHERE" statement
-`closeBracket()` | puts a closing bracket `)` at the current position in the "WHERE" statement
-`orderBy(array $expressions)` | An array of expressions to order by. `['col1', 'col2' => 'asc', 'col3' => 'desc']`
-`offset(int $offset)` | Sets the offset to start from
-`limit(int $limit)` | Sets a row limit (page size)
-`count()` | Executes the statement to perform a `SELECT COUNT(*) ...` and returns the row count as int
-`cached()` | Performs a chached query
-
-```php
-$result = $conn->select
- ->cols([
- 'uid' => ['t1.id', 'md5'],
- 'productname' => 't1.name',
- 'price' => 't1.price',
- 'type' => ['t1.type', 'upper'],
- ])
- ->from(['t1' => 'products'])
- ->where('t1.type', 'a')
- ->orderBy(['t1.price' => 'asc'])
- ->query('uid')
- ->toArray();
-```
-
-```mysql
-SELECT MD5(`t1`.`id`) AS `uid`,
- `t1`.`name` AS `productname`,
- `t1`.`price` AS `price`,
- UPPER(`t1`.`type`) AS `type`
-FROM `products` AS `t1`
-WHERE `t1`.`type` = ?
-ORDER BY `t1`.`price` ASC
-```
-
-```
-array(2) {
- 'c4ca4238a0b923820dcc509a6f75849b' =>
- array(4) {
- 'uid' =>
- string(32) "c4ca4238a0b923820dcc509a6f75849b"
- 'productname' =>
- string(8) "product1"
- 'price' =>
- string(4) "3.99"
- 'type' =>
- string(1) "A"
- }
- 'e4da3b7fbbce2345d7772b0674a318d5' =>
- array(4) {
- 'uid' =>
- string(32) "e4da3b7fbbce2345d7772b0674a318d5"
- 'productname' =>
- string(8) "product5"
- 'price' =>
- string(4) "8.19"
- 'type' =>
- string(1) "A"
- }
-}
-```
-
-
-### `Update`
-
-method | description
------- | -----------
-`table(string $tablename)` | The table to update
-`set(array $set, bool $bind = true)` | `$set` is a key/value array to update the table with. `$bind` determines whether the values should be inserted into the Query (unsafe! use only for aliases) or be replaced by parameters (the default).
-`where($val1, $val2, $operator = '=', $bind = true, $join = 'AND')` | see `Select::where()`
-`openBracket($join = null)` | see `Select::openBracket()`
-`closeBracket()` | see `Select::closeBracket()`
-
-Single row update
-```php
-$db->update
- ->table('table_to_update')
- ->set(['col_to_update' => 'val1'])
- ->where('row_id', 1)
- ->query();
-```
-
-Update multiple rows
-```php
-$values = [
- // [col_to_update, row_id]
- ['val1', 1],
- ['val2', 2],
- ['val3', 3],
-];
-
-$db->update
- ->table('table_to_update')
- ->set(['col_to_update' => '?'], false) // disable value binding here
- ->where('row_id', '?', '=', false) // also disable binding here
- ->multi($values);
-```
-
-The generated SQL for both examples would look like the following, the difference is that one performs a single query, while the other loops through the given value array in the open prepared statement.
-```mysql
-UPDATE `table_to_update` SET `col_to_update` = ? WHERE `row_id` = ?
-```
-
-### `Delete`
-
-method | description
------- | -----------
-`from(string $table)` | The table to delete from (multi table not supported yet)
-`where($val1, $val2, $operator = '=', $bind = true, $join = 'AND')` | see `Select::where()`
-`openBracket($join = null)` | see `Select::openBracket()`
-`closeBracket()` | see `Select::closeBracket()`
-
-
-## The `Result` and `ResultRow` objects
-`Result` implements `\SeekableIterator`, `\ArrayAccess` and `\Countable`, `ResultRow` extends it.
-
-### `Result`
-
-property | description
--------- | -----------
-`length` |
-
-#### methods in addition to `\SeekableIterator`, `\ArrayAccess` and `\Countable`
-
-method | description
------- | -----------
-`__construct($data = null, $sourceEncoding = null, $destEncoding = 'UTF-8')` | If `$data` is of type `\Traversable`, `\stdClass` or `array`, the `Result` will be filled with its values. If `$sourceEncoding` is present, the values will be converted to `$destEncoding` via `mb_convert_encoding()`.
-`__merge(Result $result)` | merges one `Result` object into another (using `array_merge()`)
-`chunk(int $size)` | splits the `Result` into chunks of `$size` and returns it as `array` (using `array_chunk()`)
-
-#### methods from `Enumerable`
-
-method | description
------- | -----------
-`toArray()` | returns an `array` representation of the `Result`
-`map($callback)` | collects the result of `$callback` for each value of `Result` and returns it as `array`
-`each($callback)` | similar to `map()`, except it doesn't collect results and returns the `Result` instance
-`reverse()` | reverses the order of the `Result` (using `array_reverse()`)
-
-### `ResultRow`
-`ResultRow` allows to call the result fields as magic methods or properties.
-If called as method, you may supply a `callable` as argument which then takes the field value as argument. Fancy, huh?
-
-### `Result` and `ResultRow` examples
-
-#### `map()` and `each()`
-
-```php
-$values = $result->map(function($row){
-
- // ...
-
- return [
- $row->id,
- $row->name('trim'),
- // ...
- ];
-});
-```
-
-#### `__merge()`, `toArray()`, `chunk()` and `reverse()`
-
-```php
-$result1 = new Result([['id' => 1]]);
-$result2 = new Result([['id' => 2]]);
-
-$result1->__merge($result2);
-
-var_dump($result1->toArray());
-// -> [['id' => 1], ['id' => 2]]
-
-var_dump($result1->reverse()->chunk(1)[0]);
-// -> [['id' => 2]]
-```
-