summaryrefslogtreecommitdiffstats
path: root/vendor/ipl/sql/src/Connection.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/ipl/sql/src/Connection.php')
-rw-r--r--vendor/ipl/sql/src/Connection.php554
1 files changed, 554 insertions, 0 deletions
diff --git a/vendor/ipl/sql/src/Connection.php b/vendor/ipl/sql/src/Connection.php
new file mode 100644
index 0000000..de84c72
--- /dev/null
+++ b/vendor/ipl/sql/src/Connection.php
@@ -0,0 +1,554 @@
+<?php
+
+namespace ipl\Sql;
+
+use BadMethodCallException;
+use Exception;
+use InvalidArgumentException;
+use ipl\Sql\Contract\Adapter;
+use ipl\Sql\Contract\Quoter;
+use ipl\Stdlib\Plugins;
+use PDO;
+use PDOStatement;
+
+/**
+ * Connection to a SQL database using the native PDO for database access
+ */
+class Connection implements Quoter
+{
+ use Plugins;
+
+ /** @var Config */
+ protected $config;
+
+ /** @var ?PDO */
+ protected $pdo;
+
+ /** @var QueryBuilder */
+ protected $queryBuilder;
+
+ /** @var Adapter */
+ protected $adapter;
+
+ /**
+ * Create a new database connection using the given config for initialising the options for the connection
+ *
+ * {@link init()} is called after construction.
+ *
+ * @param Config|iterable $config
+ *
+ * @throws InvalidArgumentException If there's no adapter for the given database available
+ */
+ public function __construct($config)
+ {
+ $config = $config instanceof Config ? $config : new Config($config);
+
+ $this->addPluginLoader('adapter', __NAMESPACE__ . '\\Adapter');
+
+ $adapter = $this->loadPlugin('adapter', $config->db);
+
+ if (! $adapter) {
+ throw new InvalidArgumentException("Can't load database adapter for '{$config->db}'.");
+ }
+
+ $this->adapter = new $adapter();
+ $this->config = $config;
+
+ $this->init();
+ }
+
+ /**
+ * Proxy PDO method calls
+ *
+ * @param string $name The name of the PDO method to call
+ * @param array $arguments Arguments for the method to call
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException If the called method does not exist
+ *
+ */
+ public function __call($name, array $arguments)
+ {
+ $this->connect();
+
+ if (! method_exists($this->pdo, $name)) {
+ $class = get_class($this);
+ $message = "Call to undefined method $class::$name";
+
+ throw new BadMethodCallException($message);
+ }
+
+ return call_user_func_array([$this->pdo, $name], $arguments);
+ }
+
+ /**
+ * Initialise the database connection
+ *
+ * If you have to adjust the connection after construction, override this method.
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Get the database adapter
+ *
+ * @return Adapter
+ */
+ public function getAdapter()
+ {
+ return $this->adapter;
+ }
+
+ /**
+ * Get the connection configuration
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Get the query builder for the database connection
+ *
+ * @return QueryBuilder
+ */
+ public function getQueryBuilder()
+ {
+ if ($this->queryBuilder === null) {
+ $this->queryBuilder = new QueryBuilder($this->adapter);
+ }
+
+ return $this->queryBuilder;
+ }
+
+ /**
+ * Create and return the PDO instance
+ *
+ * This method is called via {@link connect()} to establish a database connection.
+ * If the default PDO needs to be adjusted for a certain DBMS, override this method.
+ *
+ * @return PDO
+ */
+ protected function createPdoAdapter()
+ {
+ $adapter = $this->getAdapter();
+
+ $config = $this->getConfig();
+
+ return new PDO(
+ $adapter->getDsn($config),
+ $config->username,
+ $config->password,
+ $adapter->getOptions($config)
+ );
+ }
+
+ /**
+ * Connect to the database, if not already connected
+ *
+ * @return $this
+ */
+ public function connect()
+ {
+ if ($this->pdo !== null) {
+ return $this;
+ }
+
+ $this->pdo = $this->createPdoAdapter();
+
+ if (! empty($this->config->charset)) {
+ $this->exec(sprintf('SET NAMES %s', $this->pdo->quote($this->config->charset)));
+ }
+
+ $this->adapter->setClientTimezone($this);
+
+ return $this;
+ }
+
+ /**
+ * Disconnect from the database
+ *
+ * @return $this
+ */
+ public function disconnect()
+ {
+ $this->pdo = null;
+
+ return $this;
+ }
+
+ /**
+ * Check whether the connection to the database is still available
+ *
+ * @param bool $reconnect Whether to automatically reconnect
+ *
+ * @return bool
+ */
+ public function ping($reconnect = true)
+ {
+ try {
+ $this->query('SELECT 1')->closeCursor();
+ } catch (Exception $e) {
+ if (! $reconnect) {
+ return false;
+ }
+
+ $this->disconnect();
+
+ return $this->ping(false);
+ }
+
+ return true;
+ }
+
+ /**
+ * Fetch and return all result rows as sequential array
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute.
+ * @param array $values Values to bind to the statement
+ *
+ * @return array
+ */
+ public function fetchAll($stmt, array $values = null)
+ {
+ return $this->prepexec($stmt, $values)
+ ->fetchAll();
+ }
+
+ /**
+ * Fetch and return the first column of all result rows as sequential array
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute.
+ * @param array $values Values to bind to the statement
+ *
+ * @return array
+ */
+ public function fetchCol($stmt, array $values = null)
+ {
+ return $this->prepexec($stmt, $values)
+ ->fetchAll(PDO::FETCH_COLUMN, 0);
+ }
+
+ /**
+ * Fetch and return the first row of the result rows
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute.
+ * @param array $values Values to bind to the statement
+ *
+ * @return array
+ */
+ public function fetchOne($stmt, array $values = null)
+ {
+ return $this->prepexec($stmt, $values)
+ ->fetch();
+ }
+
+ /**
+ * Alias of {@link fetchOne()}
+ */
+ public function fetchRow($stmt, array $values = null)
+ {
+ return $this->prepexec($stmt, $values)
+ ->fetch();
+ }
+
+ /**
+ * Fetch and return all result rows as an array of key-value pairs
+ *
+ * First column is the key and the second column is the value.
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute.
+ * @param array $values Values to bind to the statement
+ *
+ * @return array
+ */
+ public function fetchPairs($stmt, array $values = null)
+ {
+ return $this->prepexec($stmt, $values)
+ ->fetchAll(PDO::FETCH_KEY_PAIR);
+ }
+
+ /**
+ * Fetch and return the first column of the first result row
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute.
+ * @param array $values Values to bind to the statement
+ *
+ * @return string
+ */
+ public function fetchScalar($stmt, array $values = null)
+ {
+ return $this->prepexec($stmt, $values)
+ ->fetchColumn(0);
+ }
+
+ /**
+ * Yield each result row
+ *
+ * `Connection::yieldAll(Select|string $stmt [[, array $values], int $fetchMode [, mixed ...$fetchModeOptions]])`
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute.
+ * @param mixed ...$args Values to bind to the statement, fetch mode for the statement, fetch mode options
+ *
+ * @return \Generator
+ */
+ public function yieldAll($stmt, ...$args)
+ {
+ $values = null;
+
+ if (! empty($args)) {
+ if (is_array($args[0])) {
+ $values = array_shift($args);
+ }
+ }
+
+ $fetchMode = null;
+
+ if (! empty($args)) {
+ $fetchMode = array_shift($args);
+
+ switch ($fetchMode) {
+ case PDO::FETCH_KEY_PAIR:
+ foreach ($this->yieldPairs($stmt, $values) as $key => $value) {
+ yield $key => $value;
+ }
+
+ return;
+ case PDO::FETCH_COLUMN:
+ if (empty($args)) {
+ $args[] = 0;
+ }
+
+ break;
+ }
+ }
+
+ $sth = $this->prepexec($stmt, $values);
+
+ if ($fetchMode !== null) {
+ $sth->setFetchMode($fetchMode, ...$args);
+ }
+
+ foreach ($sth as $key => $row) {
+ yield $key => $row;
+ }
+ }
+
+ /**
+ * Yield the first column of each result row
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute
+ * @param array $values Values to bind to the statement
+ *
+ * @return \Generator
+ */
+ public function yieldCol($stmt, array $values = null)
+ {
+ $sth = $this->prepexec($stmt, $values);
+
+ $sth->setFetchMode(PDO::FETCH_COLUMN, 0);
+
+ foreach ($sth as $key => $row) {
+ yield $key => $row;
+ }
+ }
+
+ /**
+ * Yield key-value pairs with the first column as key and the second column as value for each result row
+ *
+ * @param Select|string $stmt The SQL statement to prepare and execute
+ * @param array $values Values to bind to the statement
+ *
+ * @return \Generator
+ */
+ public function yieldPairs($stmt, array $values = null)
+ {
+ $sth = $this->prepexec($stmt, $values);
+
+ $sth->setFetchMode(PDO::FETCH_NUM);
+
+ foreach ($sth as $row) {
+ list($key, $value) = $row;
+
+ yield $key => $value;
+ }
+ }
+
+ /**
+ * Prepare and execute the given statement
+ *
+ * @param Delete|Insert|Select|Update|string $stmt The SQL statement to prepare and execute
+ * @param string|array $values Values to bind to the statement, if any
+ *
+ * @return PDOStatement
+ */
+ public function prepexec($stmt, $values = null)
+ {
+ if ($values !== null && ! is_array($values)) {
+ $values = [$values];
+ }
+
+ if (is_object($stmt)) {
+ list($stmt, $values) = $this->getQueryBuilder()->assemble($stmt);
+ }
+
+ $this->connect();
+
+ $sth = $this->pdo->prepare($stmt);
+ $sth->execute($values);
+
+ return $sth;
+ }
+
+ /**
+ * Prepare and execute the given Select query
+ *
+ * @param Select $select
+ *
+ * @return PDOStatement
+ */
+ public function select(Select $select)
+ {
+ list($stmt, $values) = $this->getQueryBuilder()->assembleSelect($select);
+
+ return $this->prepexec($stmt, $values);
+ }
+
+ /**
+ * Insert a table row with the specified data
+ *
+ * @param string $table The table to insert data into. The table specification must be in
+ * one of the following formats: 'table' or 'schema.table'
+ * @param iterable $data Row data in terms of column-value pairs
+ *
+ * @return PDOStatement
+ *
+ * @throws InvalidArgumentException If data type is invalid
+ */
+ public function insert($table, $data)
+ {
+ $insert = (new Insert())
+ ->into($table)
+ ->values($data);
+
+ return $this->prepexec($insert);
+ }
+
+ /**
+ * Update table rows with the specified data, optionally based on a given condition
+ *
+ * @param string|array $table The table to update. The table specification must be in one of
+ * the following formats:
+ * 'table', 'table alias', ['alias' => 'table']
+ * @param iterable $data The columns to update in terms of column-value pairs
+ * @param mixed $condition The WHERE condition
+ * @param string $operator The operator to combine multiple conditions with,
+ * if the condition is in the array format
+ *
+ * @return PDOStatement
+ *
+ * @throws InvalidArgumentException If data type is invalid
+ */
+ public function update($table, $data, $condition = null, $operator = Sql::ALL)
+ {
+ $update = (new Update())
+ ->table($table)
+ ->set($data);
+
+ if ($condition !== null) {
+ $update->where($condition, $operator);
+ }
+
+ return $this->prepexec($update);
+ }
+
+ /**
+ * Delete table rows, optionally based on a given condition
+ *
+ * @param string|array $table The table to delete data from. The table specification must be in one of the
+ * following formats: 'table', 'table alias', ['alias' => 'table']
+ * @param mixed $condition The WHERE condition
+ * @param string $operator The operator to combine multiple conditions with,
+ * if the condition is in the array format
+ *
+ * @return PDOStatement
+ */
+ public function delete($table, $condition = null, $operator = Sql::ALL)
+ {
+ $delete = (new Delete())
+ ->from($table);
+
+ if ($condition !== null) {
+ $delete->where($condition, $operator);
+ }
+
+ return $this->prepexec($delete);
+ }
+
+ /**
+ * Begin a transaction
+ *
+ * @return bool Whether the transaction was started successfully
+ */
+ public function beginTransaction()
+ {
+ $this->connect();
+
+ return $this->pdo->beginTransaction();
+ }
+
+ /**
+ * Commit a transaction
+ *
+ * @return bool Whether the transaction was committed successfully
+ */
+ public function commitTransaction()
+ {
+ return $this->pdo->commit();
+ }
+
+ /**
+ * Roll back a transaction
+ *
+ * @return bool Whether the transaction was rolled back successfully
+ */
+ public function rollBackTransaction()
+ {
+ return $this->pdo->rollBack();
+ }
+
+ /**
+ * Run the given callback in a transaction
+ *
+ * @param callable $callback The callback to run in a transaction.
+ * This connection instance is passed as parameter to the callback
+ *
+ * @return mixed The return value of the callback
+ *
+ * @throws Exception If an error occurs when running the callback
+ */
+ public function transaction(callable $callback)
+ {
+ $this->beginTransaction();
+
+ try {
+ $result = call_user_func($callback, $this);
+ $this->commitTransaction();
+ } catch (Exception $e) {
+ $this->rollBackTransaction();
+
+ throw $e;
+ }
+
+ return $result;
+ }
+
+ public function quoteIdentifier($identifier)
+ {
+ return $this->getAdapter()->quoteIdentifier($identifier);
+ }
+}