diff --git a/db/index.html b/db/index.html index fb5cd144..b00bb25a 100644 --- a/db/index.html +++ b/db/index.html @@ -804,12 +804,18 @@

Db::insert()

], ])->execute(); -

By default Insert::values(array $values, bool $add = false) and -Insert::row(array $row, bool $add = false) will set insert values removing -any previously accumulated set of values.

-

The opposite happens for Insert::rows(array $rows, bool $add = true) and -Insert::multipleValues(array $values, bool $add = true). These methods calls -will add the new rows/values provided to the existing ones.

+

By default +- Insert::values(array $values, bool $add = false) and +- Insert::row(array $row, bool $add = false) and +- Insert::rows(array $rows, bool $add = false) and +- Insert::multipleValues(array $multiple_values, bool $add = false)

+

will define the insert values removing any previously accumulated set of values.

+

The opposite happens for +- Insert::values(array $values, bool $add = true) and +- Insert::row(array $row, bool $add = true) and +- Insert::rows(array $rows, bool $add = true) and +- Insert::multipleValues(array $multiple_values, bool $add = true)

+

These methods calls will add the new rows/values provided to the existing ones.

$insert = $db->insert('product');
 
 $insert->row(['price' => 111.11, 'stock' => 111]); // Adds 1 set of values
diff --git a/search/search_index.json b/search/search_index.json
index dee88cbc..fc977c15 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"pine3ree-db","text":"

pine3ree\\Db is a small database abstraction layer on top of the \\PDO library.

It provides a simple sql builder and convenience methods for common CRUD opeations.

The DBAL instance consists of a simple PDO connection wrapper that can either use an existing connection or instantiate a new lazy pdo connection on demand.

Basic database operations for retrieving, inserting, updating and deleting rows from/into a given database table leverage a set of database Command classes, which in turn compose the database connection itself and a corresponding sql abstraction statement object.

The sql-command building operations are forwarded to the composed sql abstraction layer object, while the sql statement preparation, parameter binding and command execution are performed by the composed DBAL instance.

"},{"location":"#installation","title":"Installation","text":"

pine3ree-db DBAL requires php >= 7.4 and can be installed via composer

$ composer require pine3ree/pine3ree-db\n

The package does not provide any stable version yet, so \"minimum-stability\": \"dev\" setting is required in your composer.json file.

"},{"location":"#features","title":"Features","text":"

The library's code is splitted into two main sections/namespaces:

"},{"location":"db/","title":"pine3ree\\Db","text":""},{"location":"db/#note","title":"Note:","text":"

Unless otherwise stated the compiled sql-strings identifiers and aliases in the examples will be quoted according to the default Ansi driver, i.e. using double quotes \".

"},{"location":"db/#quick-start","title":"Quick start","text":"
use pine3ree\\Db;\nuse pine3ree\\Db\\Factory\\DbFactory;\nuse PDO;\n\n// 1. Create a dbal instance using an existing PDO connection\n$pdo = new PDO('my-db-dsn', 'my-db-username', 'my-db-password');\n$db = new Db($pdo);\n\n// 2. Create a dbal instance using pdo configuration: the PDO connection is created on demand\n$db = new Db('my-db-dsn', 'my-db-username', 'my-db-password');\n\n// 3. Create a dbal instance using a factory: the provided factory fetch a configuration\n// array from a psr-container under the `config` id/alias with specific database\n// configuration subarray under either a `db` or `pdo` key.\n$factory = new DbFactory();\n$db = $factory($container);\n\n// 4. Create a dbal instance using a factory method directly from database/pdo\n// configuration array:\n$db = DbFactory::create([\n    'driver'   => 'mysql',\n    'host'     => 'localhost',\n    'port'     => 3306,\n    'database' => 'testdb',\n    'username' => 'testuser',\n    'password' => 'secret',\n    'charset'  => 'utf8',\n]);\n\n// Simple proxy method to \\PDO::query() returning a traversable PDOStatement or\n// false if query execution fails\n$stmt = $db->query('SELECT * FROM product WHERE price < 100.0 AND id < 100');\n\n// Simple proxy method to \\PDO::exec(), returns the number of affected rows, or\n// false if execution fails\n$affected = $db->exec('UPDATE product SET published = FALSE WHERE stock <= 0');\n

Other examples:

// Fetch all rows from the \"product\" table\n// fetchAll(string $table, $where = null, $order = null, int $limit = null, int $offset = null): array\n$products = $db->fetchAll('product');\n\n// Fetch the product row with column id = 42\n// fetchOneBy(string $table, string $column, $value, $order = null): ?array\n$product = $db->fetchOneBy('product', 'id', 42);\n\n// Same row using `fetchOne()` with condition in array-format\n// fetchOne(string $table, $where = null, $order = null): ?array\n$product = $db->fetchOne('product', ['id' => 42]);\n\n$fiftyExpensiveProducts = $db->fetchAll(\n    'product', [ // conditions array start\n        ['price', '>', 1000.00], // 1 conditions in array-format\n    ], // conditions array end\n    [\n        'price' => 'ASC',\n    ],\n    50\n);\n\n$tenMostExpensiveProducts = $db->fetchAll('product', null, ['price' => 'DESC'], 10);\n\n$mostExpensiveProduct = $db->fetchOne('product', null, ['price' => 'DESC']);\n
"},{"location":"db/#constructor-arguments","title":"Constructor arguments","text":"

pine3ree\\Db supports the same constructor arguments as the \\PDO class.

It also supports an extra argument, an optional custom PDO subclass to use in lazy connection instances.

class Db\n{\n   /**\n     * @param string|PDO $dsn_or_pdo A valid pdo dsn string or an existing pdo connection instance\n     * @param string|null $username PDO connection username\n     * @param string|null $password PDO connection password\n     * @param array|null $options PDO connection options\n     * @param string|null $pdoClass An optional PDO subclass to use when creating a new connection\n     */\n    public function __construct(\n        $dsn_or_pdo,\n        string $username = null,\n        string $password = null,\n        array $options = null,\n        string $pdoClass = null\n    ) {\n}\n//...\n

The first argument can also be an existing PDO instance itself, that will be used as the composed pdo connection.

"},{"location":"db/#factory-configuration-parameters","title":"Factory configuration parameters","text":"

Factory configuration retrieved from the container should return an array like the one below:

// file config.php\nreturn [\n    // full dsn specification\n    'db' => [ // alt key: 'pdo' => [...]\n        'dns'      => 'mysql:dbname=testdb;host=localhost;port=3306;charset=utf8',\n        'username' => 'testuser', // alt key: 'user'\n        'password' => 'secret', // alt key: 'passwd' or 'pass'\n    ],\n    // ...or single parameters specs\n    'db' => [\n        'driver'   => 'mysql',\n        'dbname'   => 'testdb', // alt key: 'database'\n        'host'     => 'localhost', // alt key: 'hostname'\n        'port'     => 3306,\n        'charset'  => 'utf8',\n        'username' => 'testuser', // alt key: 'user'\n        'password' => 'secret', // alt key: 'passwd' or 'pass'\n        'options'  => [\n            // pdo-options array\n        ]\n    ],\n];\n

The database configuration subkeys depend on the db driver used and must be all in snake_case format. Please check the pdo driver page https://www.php.net/manual/en/pdo.drivers.php for more information.

The factory will attempt to build a valid pdo DSN with the provided configuration parameters.

Supported drivers are mysql, pgsql, sqlite, sqlsrv and oci.

"},{"location":"db/#crud-commands","title":"CRUD commands","text":"

To start building a crud databse command you can use the following methods:

$select = $db->select(); // returns a pine3ree\\Db\\Command\\Select instance\n$insert = $db->insert(); // returns a pine3ree\\Db\\Command\\Insert instance\n$update = $db->update(); // returns a pine3ree\\Db\\Command\\Update instance\n$delete = $db->delete(); // returns a pine3ree\\Db\\Command\\Delete instance\n

Database command instances provide a fluent interface for building sql statement. The sql build is actually perfomed by the composed sql-statement (pine3ree\\Db\\Sql\\Statement) instance with the help of the sql-driver (pine3ree\\Sql\\DriverInterface) created for the current connection.

The corresponding sql-statement objects ca be created with the following pine3ree\\Db\\Sql helper class static methods:

$select = Sql::select(); // returns a pine3ree\\Db\\Sql\\Statement\\Select instance\n$insert = Sql::insert(); // returns a pine3ree\\Db\\Sql\\Statement\\Insert instance\n$update = Sql::update(); // returns a pine3ree\\Db\\Sql\\Statement\\Update instance\n$delete = Sql::delete(); // returns a pine3ree\\Db\\Sql\\Statement\\Delete instance\n

The Sql\\Statement classes, as any other Sql\\Element class, provide a getSQL() method which compiles the sql string for the given sql-driver argument or the default Ansi driver. The sql-drivers provide identifier quoting and other sql transformations according to the underlying platform. The getSQL() method also collects user-provided parameter values along with their pdo-param types and sets named markers in their place into the sql string. The paramater collector can be retrieved by getParams() either from the sql-statement object or the wrapping command. A internal collector will be created only if not passed-in as the 2nd argument of the getSQL() call.

All database command classes implement the execute() method.

"},{"location":"db/#dbselect","title":"Db::select()","text":"

Create a pine3ree\\Db\\Command\\Select reader command instance

use pine3ree\\Db;\nuse pine3ree\\Db\\Sql;\n\n/** @var Db $db */\n\n$select = $db->select(); // create a generic empty Select command instance\n\n// SELECT * FROM \"product\"\n$select = $db->select('*', 'product');\n$select = $db->select('*')->from('product');\n$select = $db->select(null, 'product');\n$select = $db->select()->from('product');\n\n// Use table alias: SELECT * FROM \"product\" \"p\"\n$select = $db->select('*', 'product', 'p');\n$select = $db->select('*')->from('product', 'p');\n$select = $db->select()->from('product', 'p');\n\n // SELECT \"p\".\"price\", \"p\".\"vat_rate\" AS \"vatRate\" FROM \"product\" \"p\"\n$select = $db->select(['price', 'vatRate' => 'vat_rate'])->from('product', 'p');\n\n// Add where condition LessThanEqual and order-by clause\n$select->where->lte('price', 1000.0); // WHERE \"price\" <= :lte1 (named parameter marker)\n\n// ORDER BY \"p\".\"price\" ASC\n$select->orderBy('p.price', 'ASC');\n$select->orderBy('p.price', Sql::ASC);\n\n// ORDER BY \"price\" ASC, \"vat_rate\" DESC\n$select->orderBy([\n    'price' => Sql::ASC, // or just 'price' => 'ASC'\n    'vat_rate' => Sql::DESC, // or just 'vat_rate' => 'DESC'\n]);\n\n$stmt = $select->execute(); // or $select->query(), returns a \\PDOStatement instance or FALSE\n\n// SELECT\n//    \"category_id\" AS \"catId\",\n//    COUNT(*) AS \"numProducts\"\n// FROM \"product\" WHERE \"price\" > :gt1\n// GROUP BY \"category_id\"\n// HAVING \"numProducts\" >= :gte1\n$select = $db->select()\n    ->column('category_id', 'catId')\n    ->count('*', 'numProducts')\n    ->from('product');\n    ->where->gte('price', 10.0);\n// using $select->where or $select->having changes the scope and the fluent interface\n// method chain is broken\n\n// Add a GROUP BY\n// GROUP BY \"category_id\" HAVING \"numProducts\" < :lte1\n$select->groupBy('category_id')\n    ->having->lte('numProducts', 5);\n\n// SELECT MIN(\"price\") FROM \"product\" GROUP BY \"category_id\"\n$select = $db->select()->min('price')->from('product')->groupBy('category_id');\n
"},{"location":"db/#dbinsert","title":"Db::insert()","text":"

Create and optionally execute an pine3ree\\Db\\Command\\Insert writer command instance.

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2)\n$insert = $db->insert()\n    ->into('product')\n    ->row([\n        'name' => 'product-1',\n        'price' => 100.00,\n]);\n\n// equivalent to\n$insert = $db->insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values(['product-1', 100.00]);\n\n$result = $insert->execute() // or $insert->exec(), returns TRUE or FALSE for single row insert\n

Insert and execute shortcut call, when both arguments ($table and $row/$rows) are provided:

$result = $db->insert('product', [\n    'name' => 'product-111',\n    'price' => 111.11,\n]); // returns TRUE or FALSE for single insert\n\n// get the last generated value if the insert is successful\n$id = $result ? $db->lastInsertId() : null;\n

Insert many rows:

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2), (:val3, :val4)\n$num_inserted = $db->insert('product', [\n    [\n        'name' => 'product-111',\n        'price' => 111.11,\n    ],\n    [\n        'name' => 'product-222',\n        'price' => 222.22,\n    ],\n]); // returns integer or FALSE for multi-rows inserts\n\n// equivalent to\n$num_inserted = $db->insert()\n    ->into('product')\n    ->rows([\n        [\n            'name' => 'product-111',\n            'price' => 111.11,\n        ],\n        [\n            'name' => 'product-222',\n            'price' => 222.22,\n        ],\n    ])->execute(); // or exec()\n\n// and to\n$num_inserted = $db->insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values([\n        'product-111',\n        111.11,\n    ])\n    ->values([\n        'product-222',\n        222.22,\n    ], true) // The TRUE argument add values to existing values instead of replacing them\n    ->execute(); // or exec()\n\n// and to\n$num_inserted = $db->insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->multipleValues([\n        [\n            'product-111',\n            111.11,\n        ],\n        [\n            'product-222',\n            222.22,\n        ],\n    ])->execute();\n

By default Insert::values(array $values, bool $add = false) and Insert::row(array $row, bool $add = false) will set insert values removing any previously accumulated set of values.

The opposite happens for Insert::rows(array $rows, bool $add = true) and Insert::multipleValues(array $values, bool $add = true). These methods calls will add the new rows/values provided to the existing ones.

$insert = $db->insert('product');\n\n$insert->row(['price' => 111.11, 'stock' => 111]); // Adds 1 set of values\n$insert->row(['price' => 222.22, 'stock' => 222], true); // Adds 1 set of values\n// Columns \"price\" and \"stock\" are alredy specified by previuous row() calls\n$insert->values([333.33, 333], true); // Adds 1 set of values\n\n$insert->execute(); // This will try to insert 3 rows\n\n$insert->values([444.44, 444]); // Adds another set of values\n$insert->execute(); // This will try to insert 4 rows\n\n // Define the insert values after removing the old ones\n$insert->row(['price' => 555.55, 'stock' => 555], true);\n$insert->execute(); // This will try to insert 1 row\n
"},{"location":"db/#dbupdate","title":"Db::update()","text":"

The pine3ree\\Db\\Command\\Update command abstracts an INSERT operation

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// UPDATE \"product\" SET \"published\" = :set1 WHERE stock > 0\n$update = $db->update()->table('product')->set('published', true)->where('stock > 0');\n$update = $db->update('product')->set('published', true)->where('stock > 0');\n$affected = $update->execute(); // or exec()\n\n// Immediate command execution\n// UPDATE \"product\" SET \"published\" = :set1 WHERE TRUE, we use the condition \"TRUE\" to update all records\n$affected = $db->update('product', ['published' => true], 'TRUE');\n
"},{"location":"db/#dbdelete","title":"Db::delete()","text":"

The pine3ree\\Db\\Command\\Delete command abstracts a SQL DELETE operation

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// DELETE FROM \"product\" WHERE stock <= 0\n$delete = $db->delete()->from('product')->where('stock <= 0');\n$delete = $db->delete('product')->where('stock <= 0');\n$num_deleted = $delete->execute(); // or exec()\n\n// immediate command execution\n// DELETE FROM \"product\" WHERE stock <= 0\n$num_deleted = $db->delete('product', 'stock <= 0');\n
"},{"location":"db/#sql-driver-proxy-helper-methods","title":"Sql driver proxy helper methods","text":"

The following methods are simple proxies to methods implemented in the pine3ree\\Db\\Sql\\DriverInterface class of the current dbal's sql-driver instance.

"},{"location":"sql/clauses/","title":"Clauses","text":"

The abstract class pine3ree\\Db\\Sql\\Clause is the base class for implementations that abstract common SQL clauses such as the JOIN clause and the conditional clauses WHERE, HAVING and ON.

"},{"location":"sql/clauses/#the-where-having-and-on-conditional-clauses","title":"The Where, Having and On conditional clauses","text":"

A conditional clause wraps a search condition object as an instance of Predicate\\Set It also provides proxy methods to all the condition building methods of a predicate-set. After the first call of any of such methods we are brought into the context of the composed predicate-set.

\nuse pine3ree\\Db\\Sql\\Params;\nuse pine3ree\\Db\\Sql\\Predicate;\n\nConditionalClause::getSearchCondition(): Predicate\\Set;\nConditionalClause::isEmpty(): bool\nConditionalClause::hasParams(): bool\nConditionalClause::getParams(): ?Params\nConditionalClause::getSQL(DriverInterface $driver = null, Params $params = null): string\n/**\n * @param Predicate|string|array $predicate A Predicate|Predicate\\Set instance\n *      or a specs-array [identifier, operator, value] or [identifier => value]\n */\nConditionalClause::addPredicate($predicate): Predicate\\Set;\nConditionalClause::literal(string $literal): Predicate\\Set;\nConditionalClause::expression(string $expression, array $substitutions = []): Predicate\\Set;\nConditionalClause::expr(string $expression, array $substitutions = []): Predicate\\Set;\nConditionalClause::all($identifier, string $operator, Select $select): Predicate\\Set;\nConditionalClause::any($identifier, string $operator, Select $select): Predicate\\Set;\nConditionalClause::some($identifier, string $operator, Select $select): Predicate\\Set;\nConditionalClause::between($identifier, $min_value, $max_value): Predicate\\Set;\nConditionalClause::notBetween($identifier, $min_value, $max_value): Predicate\\Set;\nConditionalClause::exists(Select $select): Predicate\\Set;\nConditionalClause::notExists(Select $select): Predicate\\Set;\nConditionalClause::in($identifier, $valueList): Predicate\\Set;\nConditionalClause::notIn($identifier, $valueList): Predicate\\Set;\nConditionalClause::is($identifier, $value): Predicate\\Set;\nConditionalClause::isNot($identifier, $value): Predicate\\Set;\nConditionalClause::isNull($identifier): Predicate\\Set;\nConditionalClause::isNotNull($identifier): Predicate\\Set;\nConditionalClause::isTrue($identifier): Predicate\\Set;\nConditionalClause::isFalse($identifier): Predicate\\Set;\nConditionalClause::isUnknown($identifier): Predicate\\Set;\nConditionalClause::isNotUnknown($identifier): Predicate\\Set;\nConditionalClause::like($identifier, $pattern, string $escape = null): Predicate\\Set;\nConditionalClause::notLike($identifier, $pattern, string $escape = null): Predicate\\Set;\nConditionalClause::equal($identifier, $value): Predicate\\Set;\nConditionalClause::eq($identifier, $value): Predicate\\Set;\nConditionalClause::notEqual($identifier, $value): Predicate\\Set;\nConditionalClause::neq($identifier, $value): Predicate\\Set;\nConditionalClause::ne($identifier, $value): Predicate\\Set;\nConditionalClause::lessThan($identifier, $value): Predicate\\Set;\nConditionalClause::lt($identifier, $value): Predicate\\Set;\nConditionalClause::lessThanEqual($identifier, $value): Predicate\\Set;\nConditionalClause::lte($identifier, $value): Predicate\\Set;\nConditionalClause::greaterThanEqual($identifier, $value): Predicate\\Set;\nConditionalClause::gte($identifier, $value): Predicate\\Set;\nConditionalClause::greaterThan($identifier, $value): Predicate\\Set;\nConditionalClause::gt($identifier, $value): Predicate\\Set;\nConditionalClause::and(): Predicate\\Set;\nConditionalClause::or(): Predicate\\Set;\nConditionalClause::beginGroup(string $defaultLogicalOperator = Sql::AND): Predicate\\Set;\n\n

An endGroup() method is not provided as we call it from the search-condition Predicate\\Set context

Examples:

\nuse pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Clause\\Where;\nuse pine3ree\\Db\\Sql\\Predicate;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\n\n$date = '2023-10-07';\n\n$select = Sql::select();\n$select\n    ->from('tax_rate', 'tr')\n    ->where // changed context to the composed Where instance\n        ->lte('tr.date_min', $date) // changed context to the composed Predicate\\Set instance\n        ->and()\n        ->beginGroup() // changed context to the nested Predicate\\Set instance\n            ->equal('tr.date_max', '0000-00-00')\n            ->or()\n            ->gte('tr.date_max', $date)\n        ->endGroup(); // changed context back to the Where::$searchCondition Predicate\\Set instance\n\n// SELECT \"tr\".* FROM \"tax_rate\" \"tr\" WHERE \"tr\".\"date_min\" <= :lte1 AND (\"tr\".\"date_max\" = :eq1 OR \"tr\".\"date_max\" >= :gte1)\n\n$select = Sql::select();\n$select\n    ->columns([\n        'id',\n        'name',\n        'originalPrice' => 'price',\n    ])\n    ->column(Sql::literal('(p.price - p.discount)'), 'discountedPrice')\n    ->from('product', 'p')\n    ->where\n        ->gt('discount', 0.0)\n    ->top() // Back to the Select instance, we could have called ->up()->up(), or ->closest(Select::class)\n    ->having\n        ->lte('discountedPrice', 100.00);\n\n// SELECT \"p\".\"id\", \"p\".\"name\", \"p\".\"price\" AS \"originalPrice\", (\"p\".price - \"p\".discount) AS \"discountedPrice\"\n// FROM \"product\" \"p\"\n// WHERE \"discount\" > :gt1\n// HAVING \"discountedPrice\" <= :lte1\n
"},{"location":"sql/clauses/#the-join-clause","title":"The Join clause","text":"

The class pine3ree\\Db\\Sql\\Clause\\Join abstract the SQL JOIN clause. A Join instance is created with at least 2 parameters:

and most commonly with the following optional parameters

Examples:

\nuse pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Clause\\Join;\nuse pine3ree\\Db\\Sql\\Predicate;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\n\n$select = new Select(); // or $select = Sql::select()\n\n$select\n    ->columns([\n        'title',\n        'summary',\n        'date',\n        'author' => 'u.name', // key:alias, value: column\n    ])\n    ->from('post', 'p')\n    ->addJoin(new Join(\n        Sql::JOIN_LEFT, // or just \"LEFT\",\n        'user',\n        'u',\n        'u.id = p.user_id' // Will be used as a literal predicate for the On clause\n    ));\n\n// The resulting sql-string is split into two lines to improve readability\n//\n// SELECT \"p\".\"title\", \"p\".\"summary\", \"p\".\"date\", \"u\".\"name\" AS \"author\" FROM \"post\" \"p\"\n// LEFT JOIN \"user\" \"u\" ON (\"u\".id = \"p\".user_id)\n\n// If using a literal predicate then \"ON\" sql keyword must be included manually, i.e:\nnew Predicate\\Literal('ON u.id = p.user_id')\n

You will usually call join select method intead of programmatically creating new Join instances

\nuse pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Clause\\Join;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\n\n$select = Sql::select();\n\n$select\n    ->columns([\n        '*',\n        'author' => 'u.name',\n    ])\n    ->from('post', 'p')\n    ->leftJoin('user', 'u', [ // conditions array used to build the On clause\n        'u.id = p.user_id', // literal string\n        'u.enabled' => true, // equality condition in key => value form\n    ]);\n\n// SELECT \"p\".*, \"u\".\"name\" AS \"author\" FROM \"post\" \"p\"\n// LEFT JOIN \"user\" \"u\" ON (\"u\".id = \"p\".user_id AND \"u\".\"enabled\" = :eq1)\n

The sql Select statement class provides the following utility methods for sql-joins:

Select::addJoin(Join $join): self;\n\n/**\n * Common signature\n *\n * @param string $table The joined table name\n * @param string $alias The joined table alias\n * @param On|Predicate|Predicate\\Set|array|string|Literal|Identifier|null $specification\n * @return $this Fluent interface\n */\n\nSelect::innerJoin(string $table, string $alias, $specification = null): self;\nSelect::leftJoin(string $table, string $alias, $specification = null): self;\nSelect::rightJoin(string $table, string $alias, $specification = null): self;\nSelect::naturalJoin(string $table, string $alias, $specification = null): self;\nSelect::naturalLeftJoin(string $table, string $alias, $specification = null): self;\nSelect::naturalRightJoin(string $table, string $alias, $specification = null): self;\nSelect::crossJoin(string $table, string $alias, $specification = null): self;\nSelect::straightJoin(string $table, string $alias, $specification = null): self;\n\n
"},{"location":"sql/drivers/","title":"Drivers","text":""},{"location":"sql/drivers/#pine3reedbsqldriverinterface","title":"pine3ree\\Db\\Sql\\DriverInterface","text":""},{"location":"sql/elements/","title":"Elements","text":""},{"location":"sql/elements/#pine3reedbsqlelementinterface","title":"pine3ree\\Db\\Sql\\ElementInterface","text":"

A sql element represents full sql statements or just part of it such as identifiers, aliases, predicate, clauses, etc...

It provides a getSQL(DriverInterface $driver = null, Params $params = null) method that returns the compiled SQL-string for the elements itself with the help of the given driver and collects parameter values and types to be used when the sql-statements are being prepared to be sent to the database server.

Sql elements are also automatically organized in hierarchies when calling many elements' interface methods that add inner elements. An element can only have one and the same parent, but a cloned element has no parent assigned to it. When an element is cloned, any child element is cloned as well and then assigned to it.

Any changes to inner elements must invalidate any compiled sql-string that has been cached.

The sql Element interface provides the following methods:

\n// Return the compiled sql using the specified or the default ansi driver and\n// accumulate parameters and relative markers using the provided parameter\n// accumulator or an internal accumulator\nElementInterface::getSQL(DriverInterface $driver = null, Params $params = null): string;\n// Returns TRUE if the internal parameter accumulator exists and is not empty\nElementInterface::hasParams(): bool;\n// Returns the internal parameter accumulator, if any\nElementInterface::getParams(): ?Params;\n// Returns the parent element, if any\nElementInterface::getParent(): ?self;\n
"},{"location":"sql/elements/#pine3reedbsql","title":"pine3ree\\Db\\Sql","text":"

The Db\\Sql class offers constants for common SQL keywords and static factory methods for creating complex or simple sql elements:

use pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Alias;\nuse pine3ree\\Db\\Sql\\Expression;\nuse pine3ree\\Db\\Sql\\Literal;\nuse pine3ree\\Db\\Sql\\Identifier;\nuse pine3ree\\Db\\Sql\\Statement;\n\n// Create Identifier elements: dots are considered identifier separators\n$column = Sql::identifier('category_id'); // sql-string: \"category_id\"\n$column = Sql::identifier('p.category_id'); // sql-string: \"p\".\"category_id\"\n\n// Create sql Alias elements: dots are considered part of the alias expression\n$alias = Sql::alias('t0'); // sql-string: \"t0\"\n$alias = Sql::alias('my.Alias'); // sql-string: \"my.Alias\"\n\n// Create parametric sql Expression elements:\n// substitution parameter markers must be enclosed in curly brackets\n$expr = Sql::expression('(price * {vat_rate})', [\n    'vat_rate' => 20.0,\n]); // sql-string: (price * :expr1)\n// Using shorter method name `expr`\n// sql-string: CONCAT(:expr1, ' ', \"surname\")\n$expr = Sql::expr('CONCAT({title}, ' ', \"surname\")', ['title' => 'sir']);\n\n// Create parameter-less sql Literal expression elements:\n// substitution parameter markers must be enclosed in curly brackets\n$literal = Sql::literal('(\"price\" * 20.0)'); // sql-string: (\"price\" * 20.0)\n\n$select = Sql::select(); // returns a Statement\\Select instance\n$insert = Sql::insert(); // returns a Statement\\Insert instance\n$update = Sql::update(); // returns a Statement\\Update instance\n$delete = Sql::delete(); // returns a Statement\\Delete instance\n

All the factory methods above can be replaced with constructor calls with the same signature.

"},{"location":"sql/elements/#factory-functions","title":"Factory functions","text":"

To make code more coincise a few importable functions are provided:

use function pine3ree\\Db\\Sql\\alias as ali;\nuse function pine3ree\\Db\\Sql\\expression as xpr;\nuse function pine3ree\\Db\\Sql\\identifier as idn;\nuse function pine3ree\\Db\\Sql\\literal as lit;\n\n$column  = idn('p.category_id');\n$alias   = ali('t0');\n$expr    = xpr('(price * {vat_rate})', ['vat_rate' => 20.0]);\n$literal = lit('(\"price\" * 20.0)');\n
"},{"location":"sql/predicates/","title":"Predicates","text":""},{"location":"sql/predicates/#pine3reedbsqlpredicate-and-predicateset","title":"pine3ree\\Db\\Sql\\Predicate and Predicate\\Set","text":"

SQL predicates are parts of an sql-statement normally abstracting search-conditions inside sql clauses like WHERE, HAVING, ON. They usually resolve to a sql boolean value.

They can be part of a bigger set (predicate-set) and combined together either with an AND sql logical operator or with an OR sql logical operator. The default predicate combination of a set can be decided when calling its constructor. The default combination is AND.

A predicate-set may also be part of a bigger enclosing set. In this case the enclosed set is evaluated first and the result is combined with the other top level predicates. In a compiled sql-statement inner predicate sets are rendered enclosed in parenthesis.

The predicate-set abstraction also provides chainable factory methods for creating and adding single predicates and inner sets to itself. These methods are proxied by conditional clause classes that composes a predicate-set as their search-condition. The default logical operator is used unless the factory method is preceeded by either an Predicate\\Set::and() or a Predicate\\Set::or() chainable method call.

During sql compilation predicate identifiers are quoted as sql-identifiers. To make them to be quoted as aliases you must provide Alias instances instead of strings.

Examples:

use pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Predicate;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\nuse function pine3ree\\Db\\Sql\\alias;\n\n// Empty predicate-set with \"AND\" as default logical operator\n$predicateSet = new Predicate\\Set();\n// Add Predicate\\Comparison predicates with equality operator\n$predicateSet->equal('p.vat_rate', 20.0); // \"p\".\"vat_rate\" = :eq1\n$predicateSet->eq(alias('tot.Price'), 20.0); // AND \"tot.Price\" = :eq2\n\n$predicateSet = new Predicate\\Set([], Sql::OR); // default logical operator is \"OR\"\n$predicateSet->lessThan('vat_rate', 20.0);// \"vat_rate\" < :lt1\n$predicateSet->lt('price', 100.0); // OR \"price\" < :lt2\n// Add a Predicate\\Literal predicate with an expression used as it is\n$predicateSet->and()->literal('\"published\" IS TRUE'); // AND \"published\" IS TRUE\n$predicateSet->or()->gt('stock', 10); // OR \"stock\" > :gt1\n

As a convenience predicate-set methods may also have a shorter and/or equivalent form:

use pine3ree\\Db\\Sql\\Predicate;\n\n// Creates a Predicate\\Comparison with operator =\nPredicate\\Set::equal($identifier, $value);\nPredicate\\Set::eq($identifier, $value);\n// Creates a Predicate\\Comparison with operator !=\nPredicate\\Set::notEqual($identifier, $value);\nPredicate\\Set::neq($identifier, $value);\n// Creates a Predicate\\Comparison with operator <>\nPredicate\\Set::ne($identifier, $value);\n// Creates a Predicate\\Comparison with operator <\nPredicate\\Set::lessThan($identifier, $value);\nPredicate\\Set::lt($identifier, $value);\n// Creates a Predicate\\Comparison with operator <=\nPredicate\\Set::lessThanEqual($identifier, $value);\nPredicate\\Set::lte($identifier, $value);\n// Creates a Predicate\\Comparison with operator >=\nPredicate\\Set::greaterThanEqual($identifier, $value);\nPredicate\\Set::gte($identifier, $value);\n// Creates a Predicate\\Comparison with operator >\nPredicate\\Set::greaterThan($identifier, $value);\nPredicate\\Set::gt($identifier, $value);\n\nPredicate\\Set::like($identifier, $value, $escape); // Predicate\\Like\nPredicate\\Set::notLike($identifier, $value, $escape); // Predicate\\NotLike\n\nPredicate\\Set::between($identifier, $min, $max); // Predicate\\Between\nPredicate\\Set::notBetween($identifier, $min, $max); // Predicate\\NotBetween\n\nPredicate\\Set::in($identifier, array|Select $valueList); // Predicate\\In\nPredicate\\Set::notIn($identifier, array|Select $valueList); // Predicate\\NotIn\n\nPredicate\\Set::is($identifier, true|false|null|'UNKNOWN'); // Predicate\\Is\nPredicate\\Set::isNot($identifier, true|false|null|'UNKNOWN'); // Predicate\\IsNot\nPredicate\\Set::isNull($identifier); // Predicate\\IsNull\nPredicate\\Set::isNotNull($identifier); // Predicate\\IsNotNull\nPredicate\\Set::isTrue($identifier); // Predicate\\IsTrue\nPredicate\\Set::isFalse($identifier); // Predicate\\IsFalse\nPredicate\\Set::isUnknown($identifier); // Predicate\\IsUnknown\n\nPredicate\\Set::literal(string $literal); // Predicate\\Literal\nPredicate\\Set::expression(string $expr, array $susbtitutions); // Predicate\\Expression\nPredicate\\Set::expr(string $expr, array $susbtitutions); // Predicate\\Expression\n\nPredicate\\Set::exists($identifier, $operator, Select $select); // Predicate\\Exists\nPredicate\\Set::notExists($identifier, $operator, Select $select); // Predicate\\NotExists\n\nPredicate\\Set::all($identifier, $operator, Select $select); // Predicate\\All\nPredicate\\Set::any($identifier, $operator, Select $select); // Predicate\\Any\nPredicate\\Set::some($identifier, $operator, Select $select); // Predicate\\Some\n

Predicate sets initialized with a string will use the string to create a literal predicate:

use pine3ree\\Db\\Sql\\Predicate;\n\n// The following set will contain 1 predicate of class Predicate\\Literal\n$predicateSet = new Predicate\\Set('MAX(\"price\") <= 100.0'); // MAX(\"price\") <= 100.0\n

Subsets of predicates may be created using begingGroup() calls:

use pine3ree\\Db\\Sql\\Predicate;\n\n$predicateSet = new Predicate\\Set();\n\n// Add predicates\n\n// Begin a sub set\n// The following code will be compiled to (\"price\" > :gt1 OR \"stock\" > :gt2)\n$predicateSet\n    ->beginGroup() // entering the subset scope\n        ->gt('price', 100.0)\n        ->or()\n        ->gt('stock', 42)\n    ->endGroup() // back to the upper-level set scope\n

Predicate sets can also be created using array specifications. This is useful when used in sql statement where(), having(), on() method calls.

Examples:

use pine3ree\\Db\\Sql;\n\n$conditions = [\n    'id IS NOT NULL', // a string is converted to a literal predicate\n    ['price', '<=', 100.0], // identifier, operator, value\n    ['date_created', 'between', '2020-01-01', '2020-12-31'], // identifier, operator, value, extra-value\n    ['name', 'LIKE', 'B%'], // using the 'LIKE' exact keyword\n    ['name', 'like', 'B%'], // using the lowercase alias\n    ['name', Sql::LIKE, 'B%'], // using the Sql::LIKE constant\n    ['name', '~', 'B%'], // using the '~' alias\n    ['name', 'NOT LIKE', 'A%'], // using the 'NOT LIKE' exact keywords\n    ['name', Sql::NOT_LIKE, 'A%'], // using the Sql::NOT_LIKE constant\n    ['name', 'notLike', 'A%'], // using the lowercase 'notLike' alias\n    ['name', '!~', 'A%'], // using the '!~' alias\n    ['category_id', 'in', [11, 22, 33]], // \"category_id\" IN (:in1, :in2, :in3)\n    ['store_id', 'in', [1, 2, null]], // \"store_id\" IN (:in4, :in5) OR \"store_id\" IS NULL\n    'vat_rate' => 10.0, // identifier => value implies the equality operator\n    '||' => [ // creates a group with 'OR' as default logical operator\n        // predicate-specs-1,\n        // predicate-specs-2,\n        //...\n    ],\n    '&&' => [ // creates a group with 'AND' as default logical operator\n        // predicate-specs-1,\n        // predicate-specs-2,\n        //...,\n    ],\n ];\n\nSql::select('*')->from('product')->where($conditions);\n

Predicate specifications may use the exact sql operator string either directly or preferably via Sql class constants, or using camelCased versions such as \"notLike\". \"LIKE\" and \"NOT LIKE\" can be specified using ~ and !~ convenience aliases respectively.

"},{"location":"sql/statements/","title":"Statements","text":""},{"location":"sql/statements/#statement","title":"Statement","text":"

pine3ree\\Db\\Sql\\Statement

The SQL statement classes abstract full sql statements with named placeholder markers in place of the actual parameter values. They are composed of simpler SQL elements such as identifiers, aliases, expressions, predicates and clauses.

Statement are actually sql-statement builders and provides methods for adding the inner elements they are composed of.

Supported statements are Select for DQL, and Insert, Update, Delete for DML, reflecting the previously examined database command classes. The sql-building methods used in a command instance are proxies to corresponding methods of the composed sql-statement instance.

Note: The following examples replicates the exampes previously provided for their corresponding commands. The examples also outputs sql-strings as generated by the default ANSI sql-driver

"},{"location":"sql/statements/#sqlselect","title":"Sql::select()","text":"

Create a pine3ree\\Sql\\Statement\\Select sql statement

use pine3ree\\Db\\Sql;\n\n/** @var Db $db */\n\n$select = Sql::select(); // create a generic empty sql statement instance\n\n// SELECT * FROM \"product\"\n$select = Sql::select('*', 'product');\n$select = Sql::select('*')->from('product');\n$select = Sql::select(null, 'product');\n$select = Sql::select()->from('product');\n\n// Use table alias: SELECT \"p\".* FROM \"product\" \"p\"\n$select = Sql::select('*', 'product', 'p');\n$select = Sql::select('*')->from('product', 'p');\n$select = Sql::select()->from('product', 'p');\n\n // SELECT \"p\".\"price\", \"p\".\"vat_rate\" AS \"vatRate\" FROM \"product\" \"p\"\n$select = Sql::select(['price', 'vatRate' => 'vat_rate'])->from('product', 'p');\n\n// Add where condition LessThanEqual and order-by clause\n$select->where->lte('price', 1000.0); // WHERE \"price\" <= :lte1 (named parameter marker)\n\n// ORDER BY \"p\".\"price\" ASC\n$select->orderBy('p.price', 'ASC');\n$select->orderBy('p.price', Sql::ASC);\n\n// ORDER BY \"price\" ASC, \"vat_rate\" DESC\n$select->orderBy([\n    'price' => Sql::ASC, // or just 'price' => 'ASC'\n    'vat_rate' => Sql::DESC, // or just 'vat_rate' => 'DESC'\n]);\n\n// 1. SQL-string generated via the default singleton ANSI driver\n$sql = $select->getSQL();\n\n// 2. Using a different sql-driver, assuming we have an active PDO instance\n$driver = new Sql\\Driver\\MySQL($pdo);\n$sql = $select->getSQL($driver); // e.g. SELECT `p`.* FROM `product` `p`\n\n// 2. Using an external accumulator\n$select = Sql::select();\n$select->from('user')->where->eq('id', 42);\n$sql = $select->getSQL(null, $params);\n$params->getValues(); // [':eq1' => 42]\n\n\n// SELECT\n//    \"category_id\" AS \"catId\",\n//    COUNT(*) AS \"numProducts\"\n// FROM \"product\" WHERE \"price\" > :gt1\n// GROUP BY \"category_id\"\n// HAVING \"numProducts\" >= :gte1\n$select = Sql::select()\n    ->column('category_id', 'catId')\n    ->count('*', 'numProducts')\n    ->from('product');\n    ->where->gte('price', 10.0);\n// using $select->where or $select->having changes the scope and the fluent interface\n// method chain is broken\n\n// Add a GROUP BY\n// GROUP BY \"category_id\" HAVING \"numProducts\" < :lte1\n$select->groupBy('category_id')\n    ->having->lte('numProducts', 5);\n\n// SELECT MIN(\"price\") FROM \"product\" GROUP BY \"category_id\"\n$select = Sql::select()->min('price')->from('product')->groupBy('category_id');\n
"},{"location":"sql/statements/#sqlinsert","title":"Sql::insert()","text":"

Create a pine3ree\\Sql\\Statement\\Insert sql statement

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2)\n$insert = Sql::insert()\n    ->into('product')\n    ->row([\n        'name' => 'product-1',\n        'price' => 100.00,\n    ]);\n\n// equivalent to\n$insert = Sql::insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values(['product-1', 100.00]);\n

Insert many rows:

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2), (:val3, :val4)\nSql::insert()\n    ->into('product')\n    ->rows([\n        [\n            'name' => 'product-111',\n            'price' => 111.11,\n        ],\n        [\n            'name' => 'product-222',\n            'price' => 222.22,\n        ],\n    ]);\n\n// equivalent to\nSql::insert()\n    ->into('product')\n    ->row([\n        'name' => 'product-111',\n        'price' => 111.11,\n    ])\n    ->row([\n        'name' => 'product-222',\n        'price' => 222.22,\n    ], true); // The TRUE argument add rows to existing insert-rows instead of replacing them\n\n// and to\nSql::insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values([\n        'product-111',\n        111.11,\n    ])\n    ->values([\n        'product-222',\n        222.22,\n    ], true); // The TRUE argument add values to existing values instead of replacing them\n\n// and to\nSql::insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->multipleValues([\n        [\n            'product-111',\n            111.11,\n        ],\n        [\n            'product-222',\n            222.22,\n        ],\n    ];\n

By default Insert::values(array $values, bool $add = false) and Insert::row(array $row, bool $add = false) will define the insert values removing any previously accumulated set of values.

The opposite happens for Insert::rows(array $rows, bool $add = true) and Insert::multipleValues(array $values, bool $add = true). These methods calls will add the new rows/values provided to the existing ones.

$insert = Sql::insert('product');\n\n$insert->row(['price' => 111.11, 'stock' => 111]); // Adds 1 set of values\n$insert->row(['price' => 222.22, 'stock' => 222], true); // Adds 1 set of values\n// Columns \"price\" and \"stock\" are already specified by previous row() calls\n$insert->values([333.33, 333], true); // Adds another set of values\n\n // Set the insert values after removing the old ones\n$insert->row(['price' => 555.55, 'stock' => 555]);\n
"},{"location":"sql/statements/#sqlupdate","title":"Sql::update()","text":"

The pine3ree\\Sql\\Statement\\Update class abstracts a SQL INSERT statement

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// UPDATE \"product\" SET \"published\" = :set1 WHERE stock > 0\n$update = Sql::update()->table('product')->set('published', true)->where('stock > 0');\n$update = Sql::update('product')->set('published', true)->where('stock > 0');\n\n// Update all rows\n// UPDATE \"articles\" SET \"published\" = :set1 WHERE TRUE, we use the literal \"TRUE\" to update all records\n$update = Sql::update('articles')->set('published', false)->where(\"TRUE\");\n
"},{"location":"sql/statements/#sqldelete","title":"Sql::delete()","text":"

The pine3ree\\Sql\\Statement\\Delete class abstracts a SQL DELETE statement

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// DELETE FROM \"product\" WHERE stock <= 0\n$delete = Sql::delete()->from('product')->where('stock <= 0');\n$delete = Sql::delete('product')->where('stock <= 0');\n\n
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"pine3ree-db","text":"

pine3ree\\Db is a small database abstraction layer on top of the \\PDO library.

It provides a simple sql builder and convenience methods for common CRUD opeations.

The DBAL instance consists of a simple PDO connection wrapper that can either use an existing connection or instantiate a new lazy pdo connection on demand.

Basic database operations for retrieving, inserting, updating and deleting rows from/into a given database table leverage a set of database Command classes, which in turn compose the database connection itself and a corresponding sql abstraction statement object.

The sql-command building operations are forwarded to the composed sql abstraction layer object, while the sql statement preparation, parameter binding and command execution are performed by the composed DBAL instance.

"},{"location":"#installation","title":"Installation","text":"

pine3ree-db DBAL requires php >= 7.4 and can be installed via composer

$ composer require pine3ree/pine3ree-db\n

The package does not provide any stable version yet, so \"minimum-stability\": \"dev\" setting is required in your composer.json file.

"},{"location":"#features","title":"Features","text":"

The library's code is splitted into two main sections/namespaces:

"},{"location":"db/","title":"pine3ree\\Db","text":""},{"location":"db/#note","title":"Note:","text":"

Unless otherwise stated the compiled sql-strings identifiers and aliases in the examples will be quoted according to the default Ansi driver, i.e. using double quotes \".

"},{"location":"db/#quick-start","title":"Quick start","text":"
use pine3ree\\Db;\nuse pine3ree\\Db\\Factory\\DbFactory;\nuse PDO;\n\n// 1. Create a dbal instance using an existing PDO connection\n$pdo = new PDO('my-db-dsn', 'my-db-username', 'my-db-password');\n$db = new Db($pdo);\n\n// 2. Create a dbal instance using pdo configuration: the PDO connection is created on demand\n$db = new Db('my-db-dsn', 'my-db-username', 'my-db-password');\n\n// 3. Create a dbal instance using a factory: the provided factory fetch a configuration\n// array from a psr-container under the `config` id/alias with specific database\n// configuration subarray under either a `db` or `pdo` key.\n$factory = new DbFactory();\n$db = $factory($container);\n\n// 4. Create a dbal instance using a factory method directly from database/pdo\n// configuration array:\n$db = DbFactory::create([\n    'driver'   => 'mysql',\n    'host'     => 'localhost',\n    'port'     => 3306,\n    'database' => 'testdb',\n    'username' => 'testuser',\n    'password' => 'secret',\n    'charset'  => 'utf8',\n]);\n\n// Simple proxy method to \\PDO::query() returning a traversable PDOStatement or\n// false if query execution fails\n$stmt = $db->query('SELECT * FROM product WHERE price < 100.0 AND id < 100');\n\n// Simple proxy method to \\PDO::exec(), returns the number of affected rows, or\n// false if execution fails\n$affected = $db->exec('UPDATE product SET published = FALSE WHERE stock <= 0');\n

Other examples:

// Fetch all rows from the \"product\" table\n// fetchAll(string $table, $where = null, $order = null, int $limit = null, int $offset = null): array\n$products = $db->fetchAll('product');\n\n// Fetch the product row with column id = 42\n// fetchOneBy(string $table, string $column, $value, $order = null): ?array\n$product = $db->fetchOneBy('product', 'id', 42);\n\n// Same row using `fetchOne()` with condition in array-format\n// fetchOne(string $table, $where = null, $order = null): ?array\n$product = $db->fetchOne('product', ['id' => 42]);\n\n$fiftyExpensiveProducts = $db->fetchAll(\n    'product', [ // conditions array start\n        ['price', '>', 1000.00], // 1 conditions in array-format\n    ], // conditions array end\n    [\n        'price' => 'ASC',\n    ],\n    50\n);\n\n$tenMostExpensiveProducts = $db->fetchAll('product', null, ['price' => 'DESC'], 10);\n\n$mostExpensiveProduct = $db->fetchOne('product', null, ['price' => 'DESC']);\n
"},{"location":"db/#constructor-arguments","title":"Constructor arguments","text":"

pine3ree\\Db supports the same constructor arguments as the \\PDO class.

It also supports an extra argument, an optional custom PDO subclass to use in lazy connection instances.

class Db\n{\n   /**\n     * @param string|PDO $dsn_or_pdo A valid pdo dsn string or an existing pdo connection instance\n     * @param string|null $username PDO connection username\n     * @param string|null $password PDO connection password\n     * @param array|null $options PDO connection options\n     * @param string|null $pdoClass An optional PDO subclass to use when creating a new connection\n     */\n    public function __construct(\n        $dsn_or_pdo,\n        string $username = null,\n        string $password = null,\n        array $options = null,\n        string $pdoClass = null\n    ) {\n}\n//...\n

The first argument can also be an existing PDO instance itself, that will be used as the composed pdo connection.

"},{"location":"db/#factory-configuration-parameters","title":"Factory configuration parameters","text":"

Factory configuration retrieved from the container should return an array like the one below:

// file config.php\nreturn [\n    // full dsn specification\n    'db' => [ // alt key: 'pdo' => [...]\n        'dns'      => 'mysql:dbname=testdb;host=localhost;port=3306;charset=utf8',\n        'username' => 'testuser', // alt key: 'user'\n        'password' => 'secret', // alt key: 'passwd' or 'pass'\n    ],\n    // ...or single parameters specs\n    'db' => [\n        'driver'   => 'mysql',\n        'dbname'   => 'testdb', // alt key: 'database'\n        'host'     => 'localhost', // alt key: 'hostname'\n        'port'     => 3306,\n        'charset'  => 'utf8',\n        'username' => 'testuser', // alt key: 'user'\n        'password' => 'secret', // alt key: 'passwd' or 'pass'\n        'options'  => [\n            // pdo-options array\n        ]\n    ],\n];\n

The database configuration subkeys depend on the db driver used and must be all in snake_case format. Please check the pdo driver page https://www.php.net/manual/en/pdo.drivers.php for more information.

The factory will attempt to build a valid pdo DSN with the provided configuration parameters.

Supported drivers are mysql, pgsql, sqlite, sqlsrv and oci.

"},{"location":"db/#crud-commands","title":"CRUD commands","text":"

To start building a crud databse command you can use the following methods:

$select = $db->select(); // returns a pine3ree\\Db\\Command\\Select instance\n$insert = $db->insert(); // returns a pine3ree\\Db\\Command\\Insert instance\n$update = $db->update(); // returns a pine3ree\\Db\\Command\\Update instance\n$delete = $db->delete(); // returns a pine3ree\\Db\\Command\\Delete instance\n

Database command instances provide a fluent interface for building sql statement. The sql build is actually perfomed by the composed sql-statement (pine3ree\\Db\\Sql\\Statement) instance with the help of the sql-driver (pine3ree\\Sql\\DriverInterface) created for the current connection.

The corresponding sql-statement objects ca be created with the following pine3ree\\Db\\Sql helper class static methods:

$select = Sql::select(); // returns a pine3ree\\Db\\Sql\\Statement\\Select instance\n$insert = Sql::insert(); // returns a pine3ree\\Db\\Sql\\Statement\\Insert instance\n$update = Sql::update(); // returns a pine3ree\\Db\\Sql\\Statement\\Update instance\n$delete = Sql::delete(); // returns a pine3ree\\Db\\Sql\\Statement\\Delete instance\n

The Sql\\Statement classes, as any other Sql\\Element class, provide a getSQL() method which compiles the sql string for the given sql-driver argument or the default Ansi driver. The sql-drivers provide identifier quoting and other sql transformations according to the underlying platform. The getSQL() method also collects user-provided parameter values along with their pdo-param types and sets named markers in their place into the sql string. The paramater collector can be retrieved by getParams() either from the sql-statement object or the wrapping command. A internal collector will be created only if not passed-in as the 2nd argument of the getSQL() call.

All database command classes implement the execute() method.

"},{"location":"db/#dbselect","title":"Db::select()","text":"

Create a pine3ree\\Db\\Command\\Select reader command instance

use pine3ree\\Db;\nuse pine3ree\\Db\\Sql;\n\n/** @var Db $db */\n\n$select = $db->select(); // create a generic empty Select command instance\n\n// SELECT * FROM \"product\"\n$select = $db->select('*', 'product');\n$select = $db->select('*')->from('product');\n$select = $db->select(null, 'product');\n$select = $db->select()->from('product');\n\n// Use table alias: SELECT * FROM \"product\" \"p\"\n$select = $db->select('*', 'product', 'p');\n$select = $db->select('*')->from('product', 'p');\n$select = $db->select()->from('product', 'p');\n\n // SELECT \"p\".\"price\", \"p\".\"vat_rate\" AS \"vatRate\" FROM \"product\" \"p\"\n$select = $db->select(['price', 'vatRate' => 'vat_rate'])->from('product', 'p');\n\n// Add where condition LessThanEqual and order-by clause\n$select->where->lte('price', 1000.0); // WHERE \"price\" <= :lte1 (named parameter marker)\n\n// ORDER BY \"p\".\"price\" ASC\n$select->orderBy('p.price', 'ASC');\n$select->orderBy('p.price', Sql::ASC);\n\n// ORDER BY \"price\" ASC, \"vat_rate\" DESC\n$select->orderBy([\n    'price' => Sql::ASC, // or just 'price' => 'ASC'\n    'vat_rate' => Sql::DESC, // or just 'vat_rate' => 'DESC'\n]);\n\n$stmt = $select->execute(); // or $select->query(), returns a \\PDOStatement instance or FALSE\n\n// SELECT\n//    \"category_id\" AS \"catId\",\n//    COUNT(*) AS \"numProducts\"\n// FROM \"product\" WHERE \"price\" > :gt1\n// GROUP BY \"category_id\"\n// HAVING \"numProducts\" >= :gte1\n$select = $db->select()\n    ->column('category_id', 'catId')\n    ->count('*', 'numProducts')\n    ->from('product');\n    ->where->gte('price', 10.0);\n// using $select->where or $select->having changes the scope and the fluent interface\n// method chain is broken\n\n// Add a GROUP BY\n// GROUP BY \"category_id\" HAVING \"numProducts\" < :lte1\n$select->groupBy('category_id')\n    ->having->lte('numProducts', 5);\n\n// SELECT MIN(\"price\") FROM \"product\" GROUP BY \"category_id\"\n$select = $db->select()->min('price')->from('product')->groupBy('category_id');\n
"},{"location":"db/#dbinsert","title":"Db::insert()","text":"

Create and optionally execute an pine3ree\\Db\\Command\\Insert writer command instance.

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2)\n$insert = $db->insert()\n    ->into('product')\n    ->row([\n        'name' => 'product-1',\n        'price' => 100.00,\n]);\n\n// equivalent to\n$insert = $db->insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values(['product-1', 100.00]);\n\n$result = $insert->execute() // or $insert->exec(), returns TRUE or FALSE for single row insert\n

Insert and execute shortcut call, when both arguments ($table and $row/$rows) are provided:

$result = $db->insert('product', [\n    'name' => 'product-111',\n    'price' => 111.11,\n]); // returns TRUE or FALSE for single insert\n\n// get the last generated value if the insert is successful\n$id = $result ? $db->lastInsertId() : null;\n

Insert many rows:

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2), (:val3, :val4)\n$num_inserted = $db->insert('product', [\n    [\n        'name' => 'product-111',\n        'price' => 111.11,\n    ],\n    [\n        'name' => 'product-222',\n        'price' => 222.22,\n    ],\n]); // returns integer or FALSE for multi-rows inserts\n\n// equivalent to\n$num_inserted = $db->insert()\n    ->into('product')\n    ->rows([\n        [\n            'name' => 'product-111',\n            'price' => 111.11,\n        ],\n        [\n            'name' => 'product-222',\n            'price' => 222.22,\n        ],\n    ])->execute(); // or exec()\n\n// and to\n$num_inserted = $db->insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values([\n        'product-111',\n        111.11,\n    ])\n    ->values([\n        'product-222',\n        222.22,\n    ], true) // The TRUE argument add values to existing values instead of replacing them\n    ->execute(); // or exec()\n\n// and to\n$num_inserted = $db->insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->multipleValues([\n        [\n            'product-111',\n            111.11,\n        ],\n        [\n            'product-222',\n            222.22,\n        ],\n    ])->execute();\n

By default - Insert::values(array $values, bool $add = false) and - Insert::row(array $row, bool $add = false) and - Insert::rows(array $rows, bool $add = false) and - Insert::multipleValues(array $multiple_values, bool $add = false)

will define the insert values removing any previously accumulated set of values.

The opposite happens for - Insert::values(array $values, bool $add = true) and - Insert::row(array $row, bool $add = true) and - Insert::rows(array $rows, bool $add = true) and - Insert::multipleValues(array $multiple_values, bool $add = true)

These methods calls will add the new rows/values provided to the existing ones.

$insert = $db->insert('product');\n\n$insert->row(['price' => 111.11, 'stock' => 111]); // Adds 1 set of values\n$insert->row(['price' => 222.22, 'stock' => 222], true); // Adds 1 set of values\n// Columns \"price\" and \"stock\" are alredy specified by previuous row() calls\n$insert->values([333.33, 333], true); // Adds 1 set of values\n\n$insert->execute(); // This will try to insert 3 rows\n\n$insert->values([444.44, 444]); // Adds another set of values\n$insert->execute(); // This will try to insert 4 rows\n\n // Define the insert values after removing the old ones\n$insert->row(['price' => 555.55, 'stock' => 555], true);\n$insert->execute(); // This will try to insert 1 row\n
"},{"location":"db/#dbupdate","title":"Db::update()","text":"

The pine3ree\\Db\\Command\\Update command abstracts an INSERT operation

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// UPDATE \"product\" SET \"published\" = :set1 WHERE stock > 0\n$update = $db->update()->table('product')->set('published', true)->where('stock > 0');\n$update = $db->update('product')->set('published', true)->where('stock > 0');\n$affected = $update->execute(); // or exec()\n\n// Immediate command execution\n// UPDATE \"product\" SET \"published\" = :set1 WHERE TRUE, we use the condition \"TRUE\" to update all records\n$affected = $db->update('product', ['published' => true], 'TRUE');\n
"},{"location":"db/#dbdelete","title":"Db::delete()","text":"

The pine3ree\\Db\\Command\\Delete command abstracts a SQL DELETE operation

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// DELETE FROM \"product\" WHERE stock <= 0\n$delete = $db->delete()->from('product')->where('stock <= 0');\n$delete = $db->delete('product')->where('stock <= 0');\n$num_deleted = $delete->execute(); // or exec()\n\n// immediate command execution\n// DELETE FROM \"product\" WHERE stock <= 0\n$num_deleted = $db->delete('product', 'stock <= 0');\n
"},{"location":"db/#sql-driver-proxy-helper-methods","title":"Sql driver proxy helper methods","text":"

The following methods are simple proxies to methods implemented in the pine3ree\\Db\\Sql\\DriverInterface class of the current dbal's sql-driver instance.

"},{"location":"sql/clauses/","title":"Clauses","text":"

The abstract class pine3ree\\Db\\Sql\\Clause is the base class for implementations that abstract common SQL clauses such as the JOIN clause and the conditional clauses WHERE, HAVING and ON.

"},{"location":"sql/clauses/#the-where-having-and-on-conditional-clauses","title":"The Where, Having and On conditional clauses","text":"

A conditional clause wraps a search condition object as an instance of Predicate\\Set It also provides proxy methods to all the condition building methods of a predicate-set. After the first call of any of such methods we are brought into the context of the composed predicate-set.

\nuse pine3ree\\Db\\Sql\\Params;\nuse pine3ree\\Db\\Sql\\Predicate;\n\nConditionalClause::getSearchCondition(): Predicate\\Set;\nConditionalClause::isEmpty(): bool\nConditionalClause::hasParams(): bool\nConditionalClause::getParams(): ?Params\nConditionalClause::getSQL(DriverInterface $driver = null, Params $params = null): string\n/**\n * @param Predicate|string|array $predicate A Predicate|Predicate\\Set instance\n *      or a specs-array [identifier, operator, value] or [identifier => value]\n */\nConditionalClause::addPredicate($predicate): Predicate\\Set;\nConditionalClause::literal(string $literal): Predicate\\Set;\nConditionalClause::expression(string $expression, array $substitutions = []): Predicate\\Set;\nConditionalClause::expr(string $expression, array $substitutions = []): Predicate\\Set;\nConditionalClause::all($identifier, string $operator, Select $select): Predicate\\Set;\nConditionalClause::any($identifier, string $operator, Select $select): Predicate\\Set;\nConditionalClause::some($identifier, string $operator, Select $select): Predicate\\Set;\nConditionalClause::between($identifier, $min_value, $max_value): Predicate\\Set;\nConditionalClause::notBetween($identifier, $min_value, $max_value): Predicate\\Set;\nConditionalClause::exists(Select $select): Predicate\\Set;\nConditionalClause::notExists(Select $select): Predicate\\Set;\nConditionalClause::in($identifier, $valueList): Predicate\\Set;\nConditionalClause::notIn($identifier, $valueList): Predicate\\Set;\nConditionalClause::is($identifier, $value): Predicate\\Set;\nConditionalClause::isNot($identifier, $value): Predicate\\Set;\nConditionalClause::isNull($identifier): Predicate\\Set;\nConditionalClause::isNotNull($identifier): Predicate\\Set;\nConditionalClause::isTrue($identifier): Predicate\\Set;\nConditionalClause::isFalse($identifier): Predicate\\Set;\nConditionalClause::isUnknown($identifier): Predicate\\Set;\nConditionalClause::isNotUnknown($identifier): Predicate\\Set;\nConditionalClause::like($identifier, $pattern, string $escape = null): Predicate\\Set;\nConditionalClause::notLike($identifier, $pattern, string $escape = null): Predicate\\Set;\nConditionalClause::equal($identifier, $value): Predicate\\Set;\nConditionalClause::eq($identifier, $value): Predicate\\Set;\nConditionalClause::notEqual($identifier, $value): Predicate\\Set;\nConditionalClause::neq($identifier, $value): Predicate\\Set;\nConditionalClause::ne($identifier, $value): Predicate\\Set;\nConditionalClause::lessThan($identifier, $value): Predicate\\Set;\nConditionalClause::lt($identifier, $value): Predicate\\Set;\nConditionalClause::lessThanEqual($identifier, $value): Predicate\\Set;\nConditionalClause::lte($identifier, $value): Predicate\\Set;\nConditionalClause::greaterThanEqual($identifier, $value): Predicate\\Set;\nConditionalClause::gte($identifier, $value): Predicate\\Set;\nConditionalClause::greaterThan($identifier, $value): Predicate\\Set;\nConditionalClause::gt($identifier, $value): Predicate\\Set;\nConditionalClause::and(): Predicate\\Set;\nConditionalClause::or(): Predicate\\Set;\nConditionalClause::beginGroup(string $defaultLogicalOperator = Sql::AND): Predicate\\Set;\n\n

An endGroup() method is not provided as we call it from the search-condition Predicate\\Set context

Examples:

\nuse pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Clause\\Where;\nuse pine3ree\\Db\\Sql\\Predicate;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\n\n$date = '2023-10-07';\n\n$select = Sql::select();\n$select\n    ->from('tax_rate', 'tr')\n    ->where // changed context to the composed Where instance\n        ->lte('tr.date_min', $date) // changed context to the composed Predicate\\Set instance\n        ->and()\n        ->beginGroup() // changed context to the nested Predicate\\Set instance\n            ->equal('tr.date_max', '0000-00-00')\n            ->or()\n            ->gte('tr.date_max', $date)\n        ->endGroup(); // changed context back to the Where::$searchCondition Predicate\\Set instance\n\n// SELECT \"tr\".* FROM \"tax_rate\" \"tr\" WHERE \"tr\".\"date_min\" <= :lte1 AND (\"tr\".\"date_max\" = :eq1 OR \"tr\".\"date_max\" >= :gte1)\n\n$select = Sql::select();\n$select\n    ->columns([\n        'id',\n        'name',\n        'originalPrice' => 'price',\n    ])\n    ->column(Sql::literal('(p.price - p.discount)'), 'discountedPrice')\n    ->from('product', 'p')\n    ->where\n        ->gt('discount', 0.0)\n    ->top() // Back to the Select instance, we could have called ->up()->up(), or ->closest(Select::class)\n    ->having\n        ->lte('discountedPrice', 100.00);\n\n// SELECT \"p\".\"id\", \"p\".\"name\", \"p\".\"price\" AS \"originalPrice\", (\"p\".price - \"p\".discount) AS \"discountedPrice\"\n// FROM \"product\" \"p\"\n// WHERE \"discount\" > :gt1\n// HAVING \"discountedPrice\" <= :lte1\n
"},{"location":"sql/clauses/#the-join-clause","title":"The Join clause","text":"

The class pine3ree\\Db\\Sql\\Clause\\Join abstract the SQL JOIN clause. A Join instance is created with at least 2 parameters:

and most commonly with the following optional parameters

Examples:

\nuse pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Clause\\Join;\nuse pine3ree\\Db\\Sql\\Predicate;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\n\n$select = new Select(); // or $select = Sql::select()\n\n$select\n    ->columns([\n        'title',\n        'summary',\n        'date',\n        'author' => 'u.name', // key:alias, value: column\n    ])\n    ->from('post', 'p')\n    ->addJoin(new Join(\n        Sql::JOIN_LEFT, // or just \"LEFT\",\n        'user',\n        'u',\n        'u.id = p.user_id' // Will be used as a literal predicate for the On clause\n    ));\n\n// The resulting sql-string is split into two lines to improve readability\n//\n// SELECT \"p\".\"title\", \"p\".\"summary\", \"p\".\"date\", \"u\".\"name\" AS \"author\" FROM \"post\" \"p\"\n// LEFT JOIN \"user\" \"u\" ON (\"u\".id = \"p\".user_id)\n\n// If using a literal predicate then \"ON\" sql keyword must be included manually, i.e:\nnew Predicate\\Literal('ON u.id = p.user_id')\n

You will usually call join select method intead of programmatically creating new Join instances

\nuse pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Clause\\Join;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\n\n$select = Sql::select();\n\n$select\n    ->columns([\n        '*',\n        'author' => 'u.name',\n    ])\n    ->from('post', 'p')\n    ->leftJoin('user', 'u', [ // conditions array used to build the On clause\n        'u.id = p.user_id', // literal string\n        'u.enabled' => true, // equality condition in key => value form\n    ]);\n\n// SELECT \"p\".*, \"u\".\"name\" AS \"author\" FROM \"post\" \"p\"\n// LEFT JOIN \"user\" \"u\" ON (\"u\".id = \"p\".user_id AND \"u\".\"enabled\" = :eq1)\n

The sql Select statement class provides the following utility methods for sql-joins:

Select::addJoin(Join $join): self;\n\n/**\n * Common signature\n *\n * @param string $table The joined table name\n * @param string $alias The joined table alias\n * @param On|Predicate|Predicate\\Set|array|string|Literal|Identifier|null $specification\n * @return $this Fluent interface\n */\n\nSelect::innerJoin(string $table, string $alias, $specification = null): self;\nSelect::leftJoin(string $table, string $alias, $specification = null): self;\nSelect::rightJoin(string $table, string $alias, $specification = null): self;\nSelect::naturalJoin(string $table, string $alias, $specification = null): self;\nSelect::naturalLeftJoin(string $table, string $alias, $specification = null): self;\nSelect::naturalRightJoin(string $table, string $alias, $specification = null): self;\nSelect::crossJoin(string $table, string $alias, $specification = null): self;\nSelect::straightJoin(string $table, string $alias, $specification = null): self;\n\n
"},{"location":"sql/drivers/","title":"Drivers","text":""},{"location":"sql/drivers/#pine3reedbsqldriverinterface","title":"pine3ree\\Db\\Sql\\DriverInterface","text":""},{"location":"sql/elements/","title":"Elements","text":""},{"location":"sql/elements/#pine3reedbsqlelementinterface","title":"pine3ree\\Db\\Sql\\ElementInterface","text":"

A sql element represents full sql statements or just part of it such as identifiers, aliases, predicate, clauses, etc...

It provides a getSQL(DriverInterface $driver = null, Params $params = null) method that returns the compiled SQL-string for the elements itself with the help of the given driver and collects parameter values and types to be used when the sql-statements are being prepared to be sent to the database server.

Sql elements are also automatically organized in hierarchies when calling many elements' interface methods that add inner elements. An element can only have one and the same parent, but a cloned element has no parent assigned to it. When an element is cloned, any child element is cloned as well and then assigned to it.

Any changes to inner elements must invalidate any compiled sql-string that has been cached.

The sql Element interface provides the following methods:

\n// Return the compiled sql using the specified or the default ansi driver and\n// accumulate parameters and relative markers using the provided parameter\n// accumulator or an internal accumulator\nElementInterface::getSQL(DriverInterface $driver = null, Params $params = null): string;\n// Returns TRUE if the internal parameter accumulator exists and is not empty\nElementInterface::hasParams(): bool;\n// Returns the internal parameter accumulator, if any\nElementInterface::getParams(): ?Params;\n// Returns the parent element, if any\nElementInterface::getParent(): ?self;\n
"},{"location":"sql/elements/#pine3reedbsql","title":"pine3ree\\Db\\Sql","text":"

The Db\\Sql class offers constants for common SQL keywords and static factory methods for creating complex or simple sql elements:

use pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Alias;\nuse pine3ree\\Db\\Sql\\Expression;\nuse pine3ree\\Db\\Sql\\Literal;\nuse pine3ree\\Db\\Sql\\Identifier;\nuse pine3ree\\Db\\Sql\\Statement;\n\n// Create Identifier elements: dots are considered identifier separators\n$column = Sql::identifier('category_id'); // sql-string: \"category_id\"\n$column = Sql::identifier('p.category_id'); // sql-string: \"p\".\"category_id\"\n\n// Create sql Alias elements: dots are considered part of the alias expression\n$alias = Sql::alias('t0'); // sql-string: \"t0\"\n$alias = Sql::alias('my.Alias'); // sql-string: \"my.Alias\"\n\n// Create parametric sql Expression elements:\n// substitution parameter markers must be enclosed in curly brackets\n$expr = Sql::expression('(price * {vat_rate})', [\n    'vat_rate' => 20.0,\n]); // sql-string: (price * :expr1)\n// Using shorter method name `expr`\n// sql-string: CONCAT(:expr1, ' ', \"surname\")\n$expr = Sql::expr('CONCAT({title}, ' ', \"surname\")', ['title' => 'sir']);\n\n// Create parameter-less sql Literal expression elements:\n// substitution parameter markers must be enclosed in curly brackets\n$literal = Sql::literal('(\"price\" * 20.0)'); // sql-string: (\"price\" * 20.0)\n\n$select = Sql::select(); // returns a Statement\\Select instance\n$insert = Sql::insert(); // returns a Statement\\Insert instance\n$update = Sql::update(); // returns a Statement\\Update instance\n$delete = Sql::delete(); // returns a Statement\\Delete instance\n

All the factory methods above can be replaced with constructor calls with the same signature.

"},{"location":"sql/elements/#factory-functions","title":"Factory functions","text":"

To make code more coincise a few importable functions are provided:

use function pine3ree\\Db\\Sql\\alias as ali;\nuse function pine3ree\\Db\\Sql\\expression as xpr;\nuse function pine3ree\\Db\\Sql\\identifier as idn;\nuse function pine3ree\\Db\\Sql\\literal as lit;\n\n$column  = idn('p.category_id');\n$alias   = ali('t0');\n$expr    = xpr('(price * {vat_rate})', ['vat_rate' => 20.0]);\n$literal = lit('(\"price\" * 20.0)');\n
"},{"location":"sql/predicates/","title":"Predicates","text":""},{"location":"sql/predicates/#pine3reedbsqlpredicate-and-predicateset","title":"pine3ree\\Db\\Sql\\Predicate and Predicate\\Set","text":"

SQL predicates are parts of an sql-statement normally abstracting search-conditions inside sql clauses like WHERE, HAVING, ON. They usually resolve to a sql boolean value.

They can be part of a bigger set (predicate-set) and combined together either with an AND sql logical operator or with an OR sql logical operator. The default predicate combination of a set can be decided when calling its constructor. The default combination is AND.

A predicate-set may also be part of a bigger enclosing set. In this case the enclosed set is evaluated first and the result is combined with the other top level predicates. In a compiled sql-statement inner predicate sets are rendered enclosed in parenthesis.

The predicate-set abstraction also provides chainable factory methods for creating and adding single predicates and inner sets to itself. These methods are proxied by conditional clause classes that composes a predicate-set as their search-condition. The default logical operator is used unless the factory method is preceeded by either an Predicate\\Set::and() or a Predicate\\Set::or() chainable method call.

During sql compilation predicate identifiers are quoted as sql-identifiers. To make them to be quoted as aliases you must provide Alias instances instead of strings.

Examples:

use pine3ree\\Db\\Sql;\nuse pine3ree\\Db\\Sql\\Predicate;\nuse pine3ree\\Db\\Sql\\Statement\\Select;\nuse function pine3ree\\Db\\Sql\\alias;\n\n// Empty predicate-set with \"AND\" as default logical operator\n$predicateSet = new Predicate\\Set();\n// Add Predicate\\Comparison predicates with equality operator\n$predicateSet->equal('p.vat_rate', 20.0); // \"p\".\"vat_rate\" = :eq1\n$predicateSet->eq(alias('tot.Price'), 20.0); // AND \"tot.Price\" = :eq2\n\n$predicateSet = new Predicate\\Set([], Sql::OR); // default logical operator is \"OR\"\n$predicateSet->lessThan('vat_rate', 20.0);// \"vat_rate\" < :lt1\n$predicateSet->lt('price', 100.0); // OR \"price\" < :lt2\n// Add a Predicate\\Literal predicate with an expression used as it is\n$predicateSet->and()->literal('\"published\" IS TRUE'); // AND \"published\" IS TRUE\n$predicateSet->or()->gt('stock', 10); // OR \"stock\" > :gt1\n

As a convenience predicate-set methods may also have a shorter and/or equivalent form:

use pine3ree\\Db\\Sql\\Predicate;\n\n// Creates a Predicate\\Comparison with operator =\nPredicate\\Set::equal($identifier, $value);\nPredicate\\Set::eq($identifier, $value);\n// Creates a Predicate\\Comparison with operator !=\nPredicate\\Set::notEqual($identifier, $value);\nPredicate\\Set::neq($identifier, $value);\n// Creates a Predicate\\Comparison with operator <>\nPredicate\\Set::ne($identifier, $value);\n// Creates a Predicate\\Comparison with operator <\nPredicate\\Set::lessThan($identifier, $value);\nPredicate\\Set::lt($identifier, $value);\n// Creates a Predicate\\Comparison with operator <=\nPredicate\\Set::lessThanEqual($identifier, $value);\nPredicate\\Set::lte($identifier, $value);\n// Creates a Predicate\\Comparison with operator >=\nPredicate\\Set::greaterThanEqual($identifier, $value);\nPredicate\\Set::gte($identifier, $value);\n// Creates a Predicate\\Comparison with operator >\nPredicate\\Set::greaterThan($identifier, $value);\nPredicate\\Set::gt($identifier, $value);\n\nPredicate\\Set::like($identifier, $value, $escape); // Predicate\\Like\nPredicate\\Set::notLike($identifier, $value, $escape); // Predicate\\NotLike\n\nPredicate\\Set::between($identifier, $min, $max); // Predicate\\Between\nPredicate\\Set::notBetween($identifier, $min, $max); // Predicate\\NotBetween\n\nPredicate\\Set::in($identifier, array|Select $valueList); // Predicate\\In\nPredicate\\Set::notIn($identifier, array|Select $valueList); // Predicate\\NotIn\n\nPredicate\\Set::is($identifier, true|false|null|'UNKNOWN'); // Predicate\\Is\nPredicate\\Set::isNot($identifier, true|false|null|'UNKNOWN'); // Predicate\\IsNot\nPredicate\\Set::isNull($identifier); // Predicate\\IsNull\nPredicate\\Set::isNotNull($identifier); // Predicate\\IsNotNull\nPredicate\\Set::isTrue($identifier); // Predicate\\IsTrue\nPredicate\\Set::isFalse($identifier); // Predicate\\IsFalse\nPredicate\\Set::isUnknown($identifier); // Predicate\\IsUnknown\n\nPredicate\\Set::literal(string $literal); // Predicate\\Literal\nPredicate\\Set::expression(string $expr, array $susbtitutions); // Predicate\\Expression\nPredicate\\Set::expr(string $expr, array $susbtitutions); // Predicate\\Expression\n\nPredicate\\Set::exists($identifier, $operator, Select $select); // Predicate\\Exists\nPredicate\\Set::notExists($identifier, $operator, Select $select); // Predicate\\NotExists\n\nPredicate\\Set::all($identifier, $operator, Select $select); // Predicate\\All\nPredicate\\Set::any($identifier, $operator, Select $select); // Predicate\\Any\nPredicate\\Set::some($identifier, $operator, Select $select); // Predicate\\Some\n

Predicate sets initialized with a string will use the string to create a literal predicate:

use pine3ree\\Db\\Sql\\Predicate;\n\n// The following set will contain 1 predicate of class Predicate\\Literal\n$predicateSet = new Predicate\\Set('MAX(\"price\") <= 100.0'); // MAX(\"price\") <= 100.0\n

Subsets of predicates may be created using begingGroup() calls:

use pine3ree\\Db\\Sql\\Predicate;\n\n$predicateSet = new Predicate\\Set();\n\n// Add predicates\n\n// Begin a sub set\n// The following code will be compiled to (\"price\" > :gt1 OR \"stock\" > :gt2)\n$predicateSet\n    ->beginGroup() // entering the subset scope\n        ->gt('price', 100.0)\n        ->or()\n        ->gt('stock', 42)\n    ->endGroup() // back to the upper-level set scope\n

Predicate sets can also be created using array specifications. This is useful when used in sql statement where(), having(), on() method calls.

Examples:

use pine3ree\\Db\\Sql;\n\n$conditions = [\n    'id IS NOT NULL', // a string is converted to a literal predicate\n    ['price', '<=', 100.0], // identifier, operator, value\n    ['date_created', 'between', '2020-01-01', '2020-12-31'], // identifier, operator, value, extra-value\n    ['name', 'LIKE', 'B%'], // using the 'LIKE' exact keyword\n    ['name', 'like', 'B%'], // using the lowercase alias\n    ['name', Sql::LIKE, 'B%'], // using the Sql::LIKE constant\n    ['name', '~', 'B%'], // using the '~' alias\n    ['name', 'NOT LIKE', 'A%'], // using the 'NOT LIKE' exact keywords\n    ['name', Sql::NOT_LIKE, 'A%'], // using the Sql::NOT_LIKE constant\n    ['name', 'notLike', 'A%'], // using the lowercase 'notLike' alias\n    ['name', '!~', 'A%'], // using the '!~' alias\n    ['category_id', 'in', [11, 22, 33]], // \"category_id\" IN (:in1, :in2, :in3)\n    ['store_id', 'in', [1, 2, null]], // \"store_id\" IN (:in4, :in5) OR \"store_id\" IS NULL\n    'vat_rate' => 10.0, // identifier => value implies the equality operator\n    '||' => [ // creates a group with 'OR' as default logical operator\n        // predicate-specs-1,\n        // predicate-specs-2,\n        //...\n    ],\n    '&&' => [ // creates a group with 'AND' as default logical operator\n        // predicate-specs-1,\n        // predicate-specs-2,\n        //...,\n    ],\n ];\n\nSql::select('*')->from('product')->where($conditions);\n

Predicate specifications may use the exact sql operator string either directly or preferably via Sql class constants, or using camelCased versions such as \"notLike\". \"LIKE\" and \"NOT LIKE\" can be specified using ~ and !~ convenience aliases respectively.

"},{"location":"sql/statements/","title":"Statements","text":""},{"location":"sql/statements/#statement","title":"Statement","text":"

pine3ree\\Db\\Sql\\Statement

The SQL statement classes abstract full sql statements with named placeholder markers in place of the actual parameter values. They are composed of simpler SQL elements such as identifiers, aliases, expressions, predicates and clauses.

Statement are actually sql-statement builders and provides methods for adding the inner elements they are composed of.

Supported statements are Select for DQL, and Insert, Update, Delete for DML, reflecting the previously examined database command classes. The sql-building methods used in a command instance are proxies to corresponding methods of the composed sql-statement instance.

Note: The following examples replicates the exampes previously provided for their corresponding commands. The examples also outputs sql-strings as generated by the default ANSI sql-driver

"},{"location":"sql/statements/#sqlselect","title":"Sql::select()","text":"

Create a pine3ree\\Sql\\Statement\\Select sql statement

use pine3ree\\Db\\Sql;\n\n/** @var Db $db */\n\n$select = Sql::select(); // create a generic empty sql statement instance\n\n// SELECT * FROM \"product\"\n$select = Sql::select('*', 'product');\n$select = Sql::select('*')->from('product');\n$select = Sql::select(null, 'product');\n$select = Sql::select()->from('product');\n\n// Use table alias: SELECT \"p\".* FROM \"product\" \"p\"\n$select = Sql::select('*', 'product', 'p');\n$select = Sql::select('*')->from('product', 'p');\n$select = Sql::select()->from('product', 'p');\n\n // SELECT \"p\".\"price\", \"p\".\"vat_rate\" AS \"vatRate\" FROM \"product\" \"p\"\n$select = Sql::select(['price', 'vatRate' => 'vat_rate'])->from('product', 'p');\n\n// Add where condition LessThanEqual and order-by clause\n$select->where->lte('price', 1000.0); // WHERE \"price\" <= :lte1 (named parameter marker)\n\n// ORDER BY \"p\".\"price\" ASC\n$select->orderBy('p.price', 'ASC');\n$select->orderBy('p.price', Sql::ASC);\n\n// ORDER BY \"price\" ASC, \"vat_rate\" DESC\n$select->orderBy([\n    'price' => Sql::ASC, // or just 'price' => 'ASC'\n    'vat_rate' => Sql::DESC, // or just 'vat_rate' => 'DESC'\n]);\n\n// 1. SQL-string generated via the default singleton ANSI driver\n$sql = $select->getSQL();\n\n// 2. Using a different sql-driver, assuming we have an active PDO instance\n$driver = new Sql\\Driver\\MySQL($pdo);\n$sql = $select->getSQL($driver); // e.g. SELECT `p`.* FROM `product` `p`\n\n// 2. Using an external accumulator\n$select = Sql::select();\n$select->from('user')->where->eq('id', 42);\n$sql = $select->getSQL(null, $params);\n$params->getValues(); // [':eq1' => 42]\n\n\n// SELECT\n//    \"category_id\" AS \"catId\",\n//    COUNT(*) AS \"numProducts\"\n// FROM \"product\" WHERE \"price\" > :gt1\n// GROUP BY \"category_id\"\n// HAVING \"numProducts\" >= :gte1\n$select = Sql::select()\n    ->column('category_id', 'catId')\n    ->count('*', 'numProducts')\n    ->from('product');\n    ->where->gte('price', 10.0);\n// using $select->where or $select->having changes the scope and the fluent interface\n// method chain is broken\n\n// Add a GROUP BY\n// GROUP BY \"category_id\" HAVING \"numProducts\" < :lte1\n$select->groupBy('category_id')\n    ->having->lte('numProducts', 5);\n\n// SELECT MIN(\"price\") FROM \"product\" GROUP BY \"category_id\"\n$select = Sql::select()->min('price')->from('product')->groupBy('category_id');\n
"},{"location":"sql/statements/#sqlinsert","title":"Sql::insert()","text":"

Create a pine3ree\\Sql\\Statement\\Insert sql statement

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2)\n$insert = Sql::insert()\n    ->into('product')\n    ->row([\n        'name' => 'product-1',\n        'price' => 100.00,\n    ]);\n\n// equivalent to\n$insert = Sql::insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values(['product-1', 100.00]);\n

Insert many rows:

// INSERT INTO \"product\" (\"name\", \"price\") VALUES (:val1, :val2), (:val3, :val4)\nSql::insert()\n    ->into('product')\n    ->rows([\n        [\n            'name' => 'product-111',\n            'price' => 111.11,\n        ],\n        [\n            'name' => 'product-222',\n            'price' => 222.22,\n        ],\n    ]);\n\n// equivalent to\nSql::insert()\n    ->into('product')\n    ->row([\n        'name' => 'product-111',\n        'price' => 111.11,\n    ])\n    ->row([\n        'name' => 'product-222',\n        'price' => 222.22,\n    ], true); // The TRUE argument add rows to existing insert-rows instead of replacing them\n\n// and to\nSql::insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->values([\n        'product-111',\n        111.11,\n    ])\n    ->values([\n        'product-222',\n        222.22,\n    ], true); // The TRUE argument add values to existing values instead of replacing them\n\n// and to\nSql::insert()\n    ->into('product')\n    ->columns(['name', 'price'])\n    ->multipleValues([\n        [\n            'product-111',\n            111.11,\n        ],\n        [\n            'product-222',\n            222.22,\n        ],\n    ];\n

By default - Insert::values(array $values, bool $add = false) and - Insert::row(array $row, bool $add = false) and - Insert::rows(array $rows, bool $add = false) and - Insert::multipleValues(array $multiple_values, bool $add = false)

will define the insert values removing any previously accumulated set of values.

The opposite happens for - Insert::values(array $values, bool $add = true) and - Insert::row(array $row, bool $add = true) and - Insert::rows(array $rows, bool $add = true) and - Insert::multipleValues(array $multiple_values, bool $add = true)

These methods calls will add the new rows/values provided to the existing ones.

$insert = Sql::insert('product');\n\n$insert->row(['price' => 111.11, 'stock' => 111]); // Adds 1 set of values\n$insert->row(['price' => 222.22, 'stock' => 222], true); // Adds 1 set of values\n// Columns \"price\" and \"stock\" are already specified by previous row() calls\n$insert->values([333.33, 333], true); // Adds another set of values\n\n // Set the insert values after removing the old ones\n$insert->row(['price' => 555.55, 'stock' => 555]);\n
"},{"location":"sql/statements/#sqlupdate","title":"Sql::update()","text":"

The pine3ree\\Sql\\Statement\\Update class abstracts a SQL INSERT statement

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// UPDATE \"product\" SET \"published\" = :set1 WHERE stock > 0\n$update = Sql::update()->table('product')->set('published', true)->where('stock > 0');\n$update = Sql::update('product')->set('published', true)->where('stock > 0');\n\n// Update all rows\n// UPDATE \"articles\" SET \"published\" = :set1 WHERE TRUE, we use the literal \"TRUE\" to update all records\n$update = Sql::update('articles')->set('published', false)->where(\"TRUE\");\n
"},{"location":"sql/statements/#sqldelete","title":"Sql::delete()","text":"

The pine3ree\\Sql\\Statement\\Delete class abstracts a SQL DELETE statement

A non empty condition/predicate is required, otherwise an exception is thrown.

Examples:

// DELETE FROM \"product\" WHERE stock <= 0\n$delete = Sql::delete()->from('product')->where('stock <= 0');\n$delete = Sql::delete('product')->where('stock <= 0');\n\n
"}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index b2dccc70..0aead9cf 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ diff --git a/sql/statements/index.html b/sql/statements/index.html index 865b9548..1b8934a3 100644 --- a/sql/statements/index.html +++ b/sql/statements/index.html @@ -613,12 +613,18 @@

Sql::insert()

], ];
-

By default Insert::values(array $values, bool $add = false) and -Insert::row(array $row, bool $add = false) will define the insert values -removing any previously accumulated set of values.

-

The opposite happens for Insert::rows(array $rows, bool $add = true) and -Insert::multipleValues(array $values, bool $add = true). These methods calls -will add the new rows/values provided to the existing ones.

+

By default +- Insert::values(array $values, bool $add = false) and +- Insert::row(array $row, bool $add = false) and +- Insert::rows(array $rows, bool $add = false) and +- Insert::multipleValues(array $multiple_values, bool $add = false)

+

will define the insert values removing any previously accumulated set of values.

+

The opposite happens for +- Insert::values(array $values, bool $add = true) and +- Insert::row(array $row, bool $add = true) and +- Insert::rows(array $rows, bool $add = true) and +- Insert::multipleValues(array $multiple_values, bool $add = true)

+

These methods calls will add the new rows/values provided to the existing ones.

$insert = Sql::insert('product');
 
 $insert->row(['price' => 111.11, 'stock' => 111]); // Adds 1 set of values