diff --git a/README.md b/README.md new file mode 100644 index 0000000..51543ef --- /dev/null +++ b/README.md @@ -0,0 +1,183 @@ +### Зачем? + +Зачем очередная обёртка, спросите вы? А вот зачем: + +* Удобный 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-специфичных опций (но за описаниями +лучше залезьте в код).