php-db-drivers/README.md

184 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

### Зачем?
Зачем очередная обёртка, спросите вы? А вот зачем:
* Удобный select builder по мотивам MediaWiki-овского - не то, чтобы он был везде нужен,
да и параметры экранировать, в принципе, можно и через голое PDO...
...Но! Запросы часто нужно собирать по частям - и вот тут select builder на голых
PHP-шных массивах очень удобен. Ну и в целом - синтаксис более краток, чем PDO.
Но если не хочется - можно его и не использовать.
* Удобные функции для изменения данных - insert, upsert, update, delete,
просто по имени таблицы и переданному массиву записей
* Автоматическое переподключение
* Вложенные транзакции на SAVEPOINT'ах
* Лог запросов
Я это использую во всех проектах примерно с момента знакомства с MediaWiki.
### Лицензия и автор
Лицензия - GPL 3.0 или более новой версии
Автор - Виталий Филиппов, 2012-2017
### Создание соединения
```
$db = new DatabasePdoPgsql([
'host' => 'localhost',
'port' => 5432,
'socket' => '/var/run/postgresql',
'dbname' => '',
'username' => '',
'password' => '',
'reconnect' => true,
'tableNames' => [],
'queryLogFile' => '',
'autoBegin' => false,
'ondestroy' => 'commit',
'init' => [],
]);
```
Опции в основном говорят сами за себя. Неочевидные:
* tableNames - маппинг имён таблиц (поддерживается замена имён таблиц на уровне обёртки).
* queryLogFile - путь к файлу с логом запросов
* autoBegin - начинать ли транзакцию автоматически при первом запросе
* ondestroy - что делать при уничтожении объекта с активной транзакцией:
применить ("commit") или откатить ("rollback").
### "SELECT BUILDER"
`$db->select($tables, $fields, $where, $options, $format)`
Пример:
```
$db->select(
[
'u' => 'users',
'i' => [ 'INNER', 'instances', [ 'i.id=u.instance_id' ] ]
],
'u.*, i.name instance_name',
[
'u.reg_timestamp > ?' => time(),
'u.reg_timestamp < date_part(\'epoch\', now())',
'u.key' => [ 'A', 'B', 'C' ], // превратится в условие: u.key IN ('A', 'B', 'C')
],
[
'FOR UPDATE', // или 'FOR SHARE' или 'LOCK IN SHARE MODE'
'GROUP BY' => 'u.id',
'ORDER BY' => [ 'u.reg_timestamp' => 'ASC' ],
'LIMIT' => 10,
'OFFSET' => 10,
],
MS_HASH
);
```
По сути - это весь базовый синтаксис select builder'а. JOIN'ы могут быть LEFT, могут быть вложенными.
MS_* - формат возврата. По умолчанию возвращает массив записей БД в ассоциативном формате. MS_HASH -
значение по умолчанию, его как 4-й параметр можно не указывать, либо вместо MS_HASH можно указать:
- MS_VALUE - тогда вернётся только 1-е значение 1-й строки (скаляр)
- MS_ROW - вернётся только 1-я строка (в ассоциативном формате)
- MS_COL - вернётся только 1-я колонка (массив скаляров)
- MS_RESULT - вернётся объект результата (mysqli/PDO) для ручной обработки, результат при этом не буферизуется
Можно вызвать `$db->select("SELECT * FROM users", MS_HASH)` - это просто выполнит текстовый запрос.
В этом случае вторым параметром обязатело передать одну из констант MS_*.
Также вместо `$db->select($tables, $fields, $where, $options)` можно вызвать с теми же параметрами
`$db->select_builder($tables, $fields, $where, $options)` - он вернёт текст запроса.
### UPSERT
`$db->upsert('users', [ [ 'email' => 'vasya@pupkin.ru', 'name' => 'vasya', 'surname' => 'pupkin' ] ], [ 'email' ])` - UPSERT.
Для тех, кто знает PostgreSQL - это INSERT ... ON CONFLICT (email) DO UPDATE ...
Третий параметр - список полей уникального ключа, по которым проверяет конфликт.
Для тех, кто знает MySQL - аналог REPLACE или точнее INSERT ... ON DUPLICATE KEY UPDATE ...
### Другие функции
`$db->quote($value)` - экранирование значения
`$db->query("TRUNCATE users")` - выполнение произвольных запросов без чтения результата (только true/false)
`$db->insert_row('users', [ 'name' => 'vasya', 'surname' => 'pupkin' ])` - вставка одной строки, вернёт ID
`$db->insert('users', [ [ 'name' => 'vasya', 'surname' => 'pupkin' ], ... ])` - вставка множества строк
`$db->update('users', [ 'name' => 'vasya' ], [ 'id' => 1 ])` - эквивалентно UPDATE users SET name='vasya' WHERE id=1
Плюшка: в качестве первого параметра можно передавать JOIN'енные таблицы, в PostgreSQL
это будет корректно превращено в конструкцию UPDATE table FROM ...
Например:
```
$db->update(
[ 'u' => 'users', 'i' => [ 'INNER', 'instances', [ 'i.id=u.instance_id'] ] ],
'name' => 'vasya',
[ 'i.domain' => 'pupkin.ru' ]
);
```
`$db->delete('users', [ 'id' => [ 1, 2, 3 ] ])` - удаление по условию
В качестве первого параметра, как и в update(), можно передавать JOIN'енные таблицы, в PostgreSQL
это будет корректно превращено в конструкцию DELETE ... USING.
### Транзакции
`$db->begin(), $db->commit(), $db->rollback(), $db->commitAll(), $db->rollbackAll()`
Транзакции. Могут быть вложенные, т.е. можно несколько раз подряд сделать begin и commit/rollback.
Вложенные транзакции превращаются в SAVEPOINT'ы.
`$db->commitAll()` коммитит транзакцию со всеми активными SAVEPOINT'ами,
`$db->rollbackAll()` откатывает транзакцию со всеми активными SAVEPOINT'ами.
### VALUES
Дополнительная плюшка: обёртка для синтаксиса "inline таблиц" PostgreSQL.
```
$users = $db->select(
[
'd' => $db->values_table([
[ 'social_network' => 'vk.com', 'date' => '2017-01-01' ],
[ 'social_network' => 'facebook.com', 'date' => '2017-01-02' ],
]),
'u' => [ 'INNER', 'users', [ 'u.social_network=d.social_network', 'u.reg_date=d.date' ] ],
],
'u.*',
[]
);
```
Эквивалентно
```
SELECT u.*
FROM (VALUES ('vk.com','2017-01-01'),('facebook.com','2017-01-02')) AS d ("social_network","date")
INNER JOIN users u ON (u.social_network=d.social_network) AND (u.reg_date=d.date)
```
### MySQL
Всё вышеописанное, кроме VALUES, актуально и для MySQL - синтаксис точно такой же.
Дополнительная плюшка в MySQL - это CALC_FOUND_ROWS и функция $db->found_rows(), возвращающая
количество найденных строк. Фактически эту опцию можно использоать и в PdoPgsql-драйвере - он пробует
эмулировать её через COUNT(*) OVER() - но это не очень красивое решение.
И ещё одна плюшка - возможность использования той же самой обёртки для общения с SphinxSearch,
работающему по протоколу SphinxQL - в ней есть поддержка sphinx-специфичных опций (но за описаниями
лучше залезьте в код).