vendor/doctrine/dbal/src/Query/QueryBuilder.php line 336

  1. <?php
  2. namespace Doctrine\DBAL\Query;
  3. use Doctrine\DBAL\Cache\QueryCacheProfile;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\DBAL\Exception;
  6. use Doctrine\DBAL\ParameterType;
  7. use Doctrine\DBAL\Query\Expression\CompositeExpression;
  8. use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
  9. use Doctrine\DBAL\Result;
  10. use Doctrine\DBAL\Statement;
  11. use Doctrine\DBAL\Types\Type;
  12. use Doctrine\Deprecations\Deprecation;
  13. use function array_key_exists;
  14. use function array_keys;
  15. use function array_unshift;
  16. use function count;
  17. use function func_get_args;
  18. use function func_num_args;
  19. use function implode;
  20. use function is_array;
  21. use function is_object;
  22. use function key;
  23. use function strtoupper;
  24. use function substr;
  25. /**
  26.  * QueryBuilder class is responsible to dynamically create SQL queries.
  27.  *
  28.  * Important: Verify that every feature you use will work with your database vendor.
  29.  * SQL Query Builder does not attempt to validate the generated SQL at all.
  30.  *
  31.  * The query builder does no validation whatsoever if certain features even work with the
  32.  * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
  33.  * even if some vendors such as MySQL support it.
  34.  */
  35. class QueryBuilder
  36. {
  37.     /** @deprecated */
  38.     public const SELECT 0;
  39.     /** @deprecated */
  40.     public const DELETE 1;
  41.     /** @deprecated */
  42.     public const UPDATE 2;
  43.     /** @deprecated */
  44.     public const INSERT 3;
  45.     /** @deprecated */
  46.     public const STATE_DIRTY 0;
  47.     /** @deprecated */
  48.     public const STATE_CLEAN 1;
  49.     /**
  50.      * The DBAL Connection.
  51.      */
  52.     private Connection $connection;
  53.     /*
  54.      * The default values of SQL parts collection
  55.      */
  56.     private const SQL_PARTS_DEFAULTS = [
  57.         'select'   => [],
  58.         'distinct' => false,
  59.         'from'     => [],
  60.         'join'     => [],
  61.         'set'      => [],
  62.         'where'    => null,
  63.         'groupBy'  => [],
  64.         'having'   => null,
  65.         'orderBy'  => [],
  66.         'values'   => [],
  67.     ];
  68.     /**
  69.      * The array of SQL parts collected.
  70.      *
  71.      * @var mixed[]
  72.      */
  73.     private array $sqlParts self::SQL_PARTS_DEFAULTS;
  74.     /**
  75.      * The complete SQL string for this query.
  76.      */
  77.     private ?string $sql null;
  78.     /**
  79.      * The query parameters.
  80.      *
  81.      * @var list<mixed>|array<string, mixed>
  82.      */
  83.     private $params = [];
  84.     /**
  85.      * The parameter type map of this query.
  86.      *
  87.      * @var array<int, int|string|Type|null>|array<string, int|string|Type|null>
  88.      */
  89.     private array $paramTypes = [];
  90.     /**
  91.      * The type of query this is. Can be select, update or delete.
  92.      */
  93.     private int $type self::SELECT;
  94.     /**
  95.      * The state of the query object. Can be dirty or clean.
  96.      */
  97.     private int $state self::STATE_CLEAN;
  98.     /**
  99.      * The index of the first result to retrieve.
  100.      */
  101.     private int $firstResult 0;
  102.     /**
  103.      * The maximum number of results to retrieve or NULL to retrieve all results.
  104.      */
  105.     private ?int $maxResults null;
  106.     /**
  107.      * The counter of bound parameters used with {@see bindValue).
  108.      */
  109.     private int $boundCounter 0;
  110.     /**
  111.      * The query cache profile used for caching results.
  112.      */
  113.     private ?QueryCacheProfile $resultCacheProfile null;
  114.     /**
  115.      * Initializes a new <tt>QueryBuilder</tt>.
  116.      *
  117.      * @param Connection $connection The DBAL Connection.
  118.      */
  119.     public function __construct(Connection $connection)
  120.     {
  121.         $this->connection $connection;
  122.     }
  123.     /**
  124.      * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
  125.      * This producer method is intended for convenient inline usage. Example:
  126.      *
  127.      * <code>
  128.      *     $qb = $conn->createQueryBuilder()
  129.      *         ->select('u')
  130.      *         ->from('users', 'u')
  131.      *         ->where($qb->expr()->eq('u.id', 1));
  132.      * </code>
  133.      *
  134.      * For more complex expression construction, consider storing the expression
  135.      * builder object in a local variable.
  136.      *
  137.      * @return ExpressionBuilder
  138.      */
  139.     public function expr()
  140.     {
  141.         return $this->connection->getExpressionBuilder();
  142.     }
  143.     /**
  144.      * Gets the type of the currently built query.
  145.      *
  146.      * @deprecated If necessary, track the type of the query being built outside of the builder.
  147.      *
  148.      * @return int
  149.      */
  150.     public function getType()
  151.     {
  152.         Deprecation::trigger(
  153.             'doctrine/dbal',
  154.             'https://github.com/doctrine/dbal/pull/5551',
  155.             'Relying on the type of the query being built is deprecated.'
  156.                 ' If necessary, track the type of the query being built outside of the builder.',
  157.         );
  158.         return $this->type;
  159.     }
  160.     /**
  161.      * Gets the associated DBAL Connection for this query builder.
  162.      *
  163.      * @deprecated Use the connection used to instantiate the builder instead.
  164.      *
  165.      * @return Connection
  166.      */
  167.     public function getConnection()
  168.     {
  169.         Deprecation::trigger(
  170.             'doctrine/dbal',
  171.             'https://github.com/doctrine/dbal/pull/5780',
  172.             '%s is deprecated. Use the connection used to instantiate the builder instead.',
  173.             __METHOD__,
  174.         );
  175.         return $this->connection;
  176.     }
  177.     /**
  178.      * Gets the state of this query builder instance.
  179.      *
  180.      * @deprecated The builder state is an internal concern.
  181.      *
  182.      * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
  183.      */
  184.     public function getState()
  185.     {
  186.         Deprecation::trigger(
  187.             'doctrine/dbal',
  188.             'https://github.com/doctrine/dbal/pull/5551',
  189.             'Relying on the query builder state is deprecated as it is an internal concern.',
  190.         );
  191.         return $this->state;
  192.     }
  193.     /**
  194.      * Prepares and executes an SQL query and returns the first row of the result
  195.      * as an associative array.
  196.      *
  197.      * @return array<string, mixed>|false False is returned if no rows are found.
  198.      *
  199.      * @throws Exception
  200.      */
  201.     public function fetchAssociative()
  202.     {
  203.         return $this->executeQuery()->fetchAssociative();
  204.     }
  205.     /**
  206.      * Prepares and executes an SQL query and returns the first row of the result
  207.      * as a numerically indexed array.
  208.      *
  209.      * @return array<int, mixed>|false False is returned if no rows are found.
  210.      *
  211.      * @throws Exception
  212.      */
  213.     public function fetchNumeric()
  214.     {
  215.         return $this->executeQuery()->fetchNumeric();
  216.     }
  217.     /**
  218.      * Prepares and executes an SQL query and returns the value of a single column
  219.      * of the first row of the result.
  220.      *
  221.      * @return mixed|false False is returned if no rows are found.
  222.      *
  223.      * @throws Exception
  224.      */
  225.     public function fetchOne()
  226.     {
  227.         return $this->executeQuery()->fetchOne();
  228.     }
  229.     /**
  230.      * Prepares and executes an SQL query and returns the result as an array of numeric arrays.
  231.      *
  232.      * @return array<int,array<int,mixed>>
  233.      *
  234.      * @throws Exception
  235.      */
  236.     public function fetchAllNumeric(): array
  237.     {
  238.         return $this->executeQuery()->fetchAllNumeric();
  239.     }
  240.     /**
  241.      * Prepares and executes an SQL query and returns the result as an array of associative arrays.
  242.      *
  243.      * @return array<int,array<string,mixed>>
  244.      *
  245.      * @throws Exception
  246.      */
  247.     public function fetchAllAssociative(): array
  248.     {
  249.         return $this->executeQuery()->fetchAllAssociative();
  250.     }
  251.     /**
  252.      * Prepares and executes an SQL query and returns the result as an associative array with the keys
  253.      * mapped to the first column and the values mapped to the second column.
  254.      *
  255.      * @return array<mixed,mixed>
  256.      *
  257.      * @throws Exception
  258.      */
  259.     public function fetchAllKeyValue(): array
  260.     {
  261.         return $this->executeQuery()->fetchAllKeyValue();
  262.     }
  263.     /**
  264.      * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped
  265.      * to the first column and the values being an associative array representing the rest of the columns
  266.      * and their values.
  267.      *
  268.      * @return array<mixed,array<string,mixed>>
  269.      *
  270.      * @throws Exception
  271.      */
  272.     public function fetchAllAssociativeIndexed(): array
  273.     {
  274.         return $this->executeQuery()->fetchAllAssociativeIndexed();
  275.     }
  276.     /**
  277.      * Prepares and executes an SQL query and returns the result as an array of the first column values.
  278.      *
  279.      * @return array<int,mixed>
  280.      *
  281.      * @throws Exception
  282.      */
  283.     public function fetchFirstColumn(): array
  284.     {
  285.         return $this->executeQuery()->fetchFirstColumn();
  286.     }
  287.     /**
  288.      * Executes an SQL query (SELECT) and returns a Result.
  289.      *
  290.      * @throws Exception
  291.      */
  292.     public function executeQuery(): Result
  293.     {
  294.         return $this->connection->executeQuery(
  295.             $this->getSQL(),
  296.             $this->params,
  297.             $this->paramTypes,
  298.             $this->resultCacheProfile,
  299.         );
  300.     }
  301.     /**
  302.      * Executes an SQL statement and returns the number of affected rows.
  303.      *
  304.      * Should be used for INSERT, UPDATE and DELETE
  305.      *
  306.      * @return int The number of affected rows.
  307.      *
  308.      * @throws Exception
  309.      */
  310.     public function executeStatement(): int
  311.     {
  312.         return $this->connection->executeStatement($this->getSQL(), $this->params$this->paramTypes);
  313.     }
  314.     /**
  315.      * Executes this query using the bound parameters and their types.
  316.      *
  317.      * @deprecated Use {@see executeQuery()} or {@see executeStatement()} instead.
  318.      *
  319.      * @return Result|int|string
  320.      *
  321.      * @throws Exception
  322.      */
  323.     public function execute()
  324.     {
  325.         if ($this->type === self::SELECT) {
  326.             Deprecation::trigger(
  327.                 'doctrine/dbal',
  328.                 'https://github.com/doctrine/dbal/pull/4578',
  329.                 'QueryBuilder::execute() is deprecated, use QueryBuilder::executeQuery() for SQL queries instead.',
  330.             );
  331.             return $this->executeQuery();
  332.         }
  333.         Deprecation::trigger(
  334.             'doctrine/dbal',
  335.             'https://github.com/doctrine/dbal/pull/4578',
  336.             'QueryBuilder::execute() is deprecated, use QueryBuilder::executeStatement() for SQL statements instead.',
  337.         );
  338.         return $this->connection->executeStatement($this->getSQL(), $this->params$this->paramTypes);
  339.     }
  340.     /**
  341.      * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
  342.      *
  343.      * <code>
  344.      *     $qb = $em->createQueryBuilder()
  345.      *         ->select('u')
  346.      *         ->from('User', 'u')
  347.      *     echo $qb->getSQL(); // SELECT u FROM User u
  348.      * </code>
  349.      *
  350.      * @return string The SQL query string.
  351.      */
  352.     public function getSQL()
  353.     {
  354.         if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
  355.             return $this->sql;
  356.         }
  357.         switch ($this->type) {
  358.             case self::INSERT:
  359.                 $sql $this->getSQLForInsert();
  360.                 break;
  361.             case self::DELETE:
  362.                 $sql $this->getSQLForDelete();
  363.                 break;
  364.             case self::UPDATE:
  365.                 $sql $this->getSQLForUpdate();
  366.                 break;
  367.             case self::SELECT:
  368.             default:
  369.                 $sql $this->getSQLForSelect();
  370.                 break;
  371.         }
  372.         $this->state self::STATE_CLEAN;
  373.         $this->sql   $sql;
  374.         return $sql;
  375.     }
  376.     /**
  377.      * Sets a query parameter for the query being constructed.
  378.      *
  379.      * <code>
  380.      *     $qb = $conn->createQueryBuilder()
  381.      *         ->select('u')
  382.      *         ->from('users', 'u')
  383.      *         ->where('u.id = :user_id')
  384.      *         ->setParameter('user_id', 1);
  385.      * </code>
  386.      *
  387.      * @param int|string           $key   Parameter position or name
  388.      * @param mixed                $value Parameter value
  389.      * @param int|string|Type|null $type  Parameter type
  390.      *
  391.      * @return $this This QueryBuilder instance.
  392.      */
  393.     public function setParameter($key$value$type ParameterType::STRING)
  394.     {
  395.         if ($type !== null) {
  396.             $this->paramTypes[$key] = $type;
  397.         } else {
  398.             Deprecation::trigger(
  399.                 'doctrine/dbal',
  400.                 'https://github.com/doctrine/dbal/pull/5550',
  401.                 'Using NULL as prepared statement parameter type is deprecated.'
  402.                     'Omit or use Parameter::STRING instead',
  403.             );
  404.         }
  405.         $this->params[$key] = $value;
  406.         return $this;
  407.     }
  408.     /**
  409.      * Sets a collection of query parameters for the query being constructed.
  410.      *
  411.      * <code>
  412.      *     $qb = $conn->createQueryBuilder()
  413.      *         ->select('u')
  414.      *         ->from('users', 'u')
  415.      *         ->where('u.id = :user_id1 OR u.id = :user_id2')
  416.      *         ->setParameters(array(
  417.      *             'user_id1' => 1,
  418.      *             'user_id2' => 2
  419.      *         ));
  420.      * </code>
  421.      *
  422.      * @param list<mixed>|array<string, mixed>                                     $params Parameters to set
  423.      * @param array<int, int|string|Type|null>|array<string, int|string|Type|null> $types  Parameter types
  424.      *
  425.      * @return $this This QueryBuilder instance.
  426.      */
  427.     public function setParameters(array $params, array $types = [])
  428.     {
  429.         $this->paramTypes $types;
  430.         $this->params     $params;
  431.         return $this;
  432.     }
  433.     /**
  434.      * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
  435.      *
  436.      * @return list<mixed>|array<string, mixed> The currently defined query parameters
  437.      */
  438.     public function getParameters()
  439.     {
  440.         return $this->params;
  441.     }
  442.     /**
  443.      * Gets a (previously set) query parameter of the query being constructed.
  444.      *
  445.      * @param mixed $key The key (index or name) of the bound parameter.
  446.      *
  447.      * @return mixed The value of the bound parameter.
  448.      */
  449.     public function getParameter($key)
  450.     {
  451.         return $this->params[$key] ?? null;
  452.     }
  453.     /**
  454.      * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
  455.      *
  456.      * @return array<int, int|string|Type|null>|array<string, int|string|Type|null> The currently defined
  457.      *                                                                              query parameter types
  458.      */
  459.     public function getParameterTypes()
  460.     {
  461.         return $this->paramTypes;
  462.     }
  463.     /**
  464.      * Gets a (previously set) query parameter type of the query being constructed.
  465.      *
  466.      * @param int|string $key The key of the bound parameter type
  467.      *
  468.      * @return int|string|Type The value of the bound parameter type
  469.      */
  470.     public function getParameterType($key)
  471.     {
  472.         return $this->paramTypes[$key] ?? ParameterType::STRING;
  473.     }
  474.     /**
  475.      * Sets the position of the first result to retrieve (the "offset").
  476.      *
  477.      * @param int $firstResult The first result to return.
  478.      *
  479.      * @return $this This QueryBuilder instance.
  480.      */
  481.     public function setFirstResult($firstResult)
  482.     {
  483.         $this->state       self::STATE_DIRTY;
  484.         $this->firstResult $firstResult;
  485.         return $this;
  486.     }
  487.     /**
  488.      * Gets the position of the first result the query object was set to retrieve (the "offset").
  489.      *
  490.      * @return int The position of the first result.
  491.      */
  492.     public function getFirstResult()
  493.     {
  494.         return $this->firstResult;
  495.     }
  496.     /**
  497.      * Sets the maximum number of results to retrieve (the "limit").
  498.      *
  499.      * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results.
  500.      *
  501.      * @return $this This QueryBuilder instance.
  502.      */
  503.     public function setMaxResults($maxResults)
  504.     {
  505.         $this->state      self::STATE_DIRTY;
  506.         $this->maxResults $maxResults;
  507.         return $this;
  508.     }
  509.     /**
  510.      * Gets the maximum number of results the query object was set to retrieve (the "limit").
  511.      * Returns NULL if all results will be returned.
  512.      *
  513.      * @return int|null The maximum number of results.
  514.      */
  515.     public function getMaxResults()
  516.     {
  517.         return $this->maxResults;
  518.     }
  519.     /**
  520.      * Either appends to or replaces a single, generic query part.
  521.      *
  522.      * The available parts are: 'select', 'from', 'set', 'where',
  523.      * 'groupBy', 'having' and 'orderBy'.
  524.      *
  525.      * @param string $sqlPartName
  526.      * @param mixed  $sqlPart
  527.      * @param bool   $append
  528.      *
  529.      * @return $this This QueryBuilder instance.
  530.      */
  531.     public function add($sqlPartName$sqlPart$append false)
  532.     {
  533.         $isArray    is_array($sqlPart);
  534.         $isMultiple is_array($this->sqlParts[$sqlPartName]);
  535.         if ($isMultiple && ! $isArray) {
  536.             $sqlPart = [$sqlPart];
  537.         }
  538.         $this->state self::STATE_DIRTY;
  539.         if ($append) {
  540.             if (
  541.                 $sqlPartName === 'orderBy'
  542.                 || $sqlPartName === 'groupBy'
  543.                 || $sqlPartName === 'select'
  544.                 || $sqlPartName === 'set'
  545.             ) {
  546.                 foreach ($sqlPart as $part) {
  547.                     $this->sqlParts[$sqlPartName][] = $part;
  548.                 }
  549.             } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) {
  550.                 $key                                  key($sqlPart);
  551.                 $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
  552.             } elseif ($isMultiple) {
  553.                 $this->sqlParts[$sqlPartName][] = $sqlPart;
  554.             } else {
  555.                 $this->sqlParts[$sqlPartName] = $sqlPart;
  556.             }
  557.             return $this;
  558.         }
  559.         $this->sqlParts[$sqlPartName] = $sqlPart;
  560.         return $this;
  561.     }
  562.     /**
  563.      * Specifies an item that is to be returned in the query result.
  564.      * Replaces any previously specified selections, if any.
  565.      *
  566.      * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument.
  567.      *
  568.      * <code>
  569.      *     $qb = $conn->createQueryBuilder()
  570.      *         ->select('u.id', 'p.id')
  571.      *         ->from('users', 'u')
  572.      *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
  573.      * </code>
  574.      *
  575.      * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED.
  576.      *                                     Pass each value as an individual argument.
  577.      *
  578.      * @return $this This QueryBuilder instance.
  579.      */
  580.     public function select($select null/*, string ...$selects*/)
  581.     {
  582.         $this->type self::SELECT;
  583.         if ($select === null) {
  584.             return $this;
  585.         }
  586.         if (is_array($select)) {
  587.             Deprecation::trigger(
  588.                 'doctrine/dbal',
  589.                 'https://github.com/doctrine/dbal/issues/3837',
  590.                 'Passing an array for the first argument to QueryBuilder::select() is deprecated, ' .
  591.                 'pass each value as an individual variadic argument instead.',
  592.             );
  593.         }
  594.         $selects is_array($select) ? $select func_get_args();
  595.         return $this->add('select'$selects);
  596.     }
  597.     /**
  598.      * Adds DISTINCT to the query.
  599.      *
  600.      * <code>
  601.      *     $qb = $conn->createQueryBuilder()
  602.      *         ->select('u.id')
  603.      *         ->distinct()
  604.      *         ->from('users', 'u')
  605.      * </code>
  606.      *
  607.      * @return $this This QueryBuilder instance.
  608.      */
  609.     public function distinct(): self
  610.     {
  611.         $this->sqlParts['distinct'] = true;
  612.         return $this;
  613.     }
  614.     /**
  615.      * Adds an item that is to be returned in the query result.
  616.      *
  617.      * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument.
  618.      *
  619.      * <code>
  620.      *     $qb = $conn->createQueryBuilder()
  621.      *         ->select('u.id')
  622.      *         ->addSelect('p.id')
  623.      *         ->from('users', 'u')
  624.      *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
  625.      * </code>
  626.      *
  627.      * @param string|string[]|null $select The selection expression. USING AN ARRAY OR NULL IS DEPRECATED.
  628.      *                                     Pass each value as an individual argument.
  629.      *
  630.      * @return $this This QueryBuilder instance.
  631.      */
  632.     public function addSelect($select null/*, string ...$selects*/)
  633.     {
  634.         $this->type self::SELECT;
  635.         if ($select === null) {
  636.             return $this;
  637.         }
  638.         if (is_array($select)) {
  639.             Deprecation::trigger(
  640.                 'doctrine/dbal',
  641.                 'https://github.com/doctrine/dbal/issues/3837',
  642.                 'Passing an array for the first argument to QueryBuilder::addSelect() is deprecated, ' .
  643.                 'pass each value as an individual variadic argument instead.',
  644.             );
  645.         }
  646.         $selects is_array($select) ? $select func_get_args();
  647.         return $this->add('select'$selectstrue);
  648.     }
  649.     /**
  650.      * Turns the query being built into a bulk delete query that ranges over
  651.      * a certain table.
  652.      *
  653.      * <code>
  654.      *     $qb = $conn->createQueryBuilder()
  655.      *         ->delete('users', 'u')
  656.      *         ->where('u.id = :user_id')
  657.      *         ->setParameter(':user_id', 1);
  658.      * </code>
  659.      *
  660.      * @param string $delete The table whose rows are subject to the deletion.
  661.      * @param string $alias  The table alias used in the constructed query.
  662.      *
  663.      * @return $this This QueryBuilder instance.
  664.      */
  665.     public function delete($delete null$alias null)
  666.     {
  667.         $this->type self::DELETE;
  668.         if ($delete === null) {
  669.             return $this;
  670.         }
  671.         return $this->add('from', [
  672.             'table' => $delete,
  673.             'alias' => $alias,
  674.         ]);
  675.     }
  676.     /**
  677.      * Turns the query being built into a bulk update query that ranges over
  678.      * a certain table
  679.      *
  680.      * <code>
  681.      *     $qb = $conn->createQueryBuilder()
  682.      *         ->update('counters', 'c')
  683.      *         ->set('c.value', 'c.value + 1')
  684.      *         ->where('c.id = ?');
  685.      * </code>
  686.      *
  687.      * @param string $update The table whose rows are subject to the update.
  688.      * @param string $alias  The table alias used in the constructed query.
  689.      *
  690.      * @return $this This QueryBuilder instance.
  691.      */
  692.     public function update($update null$alias null)
  693.     {
  694.         $this->type self::UPDATE;
  695.         if ($update === null) {
  696.             return $this;
  697.         }
  698.         return $this->add('from', [
  699.             'table' => $update,
  700.             'alias' => $alias,
  701.         ]);
  702.     }
  703.     /**
  704.      * Turns the query being built into an insert query that inserts into
  705.      * a certain table
  706.      *
  707.      * <code>
  708.      *     $qb = $conn->createQueryBuilder()
  709.      *         ->insert('users')
  710.      *         ->values(
  711.      *             array(
  712.      *                 'name' => '?',
  713.      *                 'password' => '?'
  714.      *             )
  715.      *         );
  716.      * </code>
  717.      *
  718.      * @param string $insert The table into which the rows should be inserted.
  719.      *
  720.      * @return $this This QueryBuilder instance.
  721.      */
  722.     public function insert($insert null)
  723.     {
  724.         $this->type self::INSERT;
  725.         if ($insert === null) {
  726.             return $this;
  727.         }
  728.         return $this->add('from', ['table' => $insert]);
  729.     }
  730.     /**
  731.      * Creates and adds a query root corresponding to the table identified by the
  732.      * given alias, forming a cartesian product with any existing query roots.
  733.      *
  734.      * <code>
  735.      *     $qb = $conn->createQueryBuilder()
  736.      *         ->select('u.id')
  737.      *         ->from('users', 'u')
  738.      * </code>
  739.      *
  740.      * @param string      $from  The table.
  741.      * @param string|null $alias The alias of the table.
  742.      *
  743.      * @return $this This QueryBuilder instance.
  744.      */
  745.     public function from($from$alias null)
  746.     {
  747.         return $this->add('from', [
  748.             'table' => $from,
  749.             'alias' => $alias,
  750.         ], true);
  751.     }
  752.     /**
  753.      * Creates and adds a join to the query.
  754.      *
  755.      * <code>
  756.      *     $qb = $conn->createQueryBuilder()
  757.      *         ->select('u.name')
  758.      *         ->from('users', 'u')
  759.      *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
  760.      * </code>
  761.      *
  762.      * @param string $fromAlias The alias that points to a from clause.
  763.      * @param string $join      The table name to join.
  764.      * @param string $alias     The alias of the join table.
  765.      * @param string $condition The condition for the join.
  766.      *
  767.      * @return $this This QueryBuilder instance.
  768.      */
  769.     public function join($fromAlias$join$alias$condition null)
  770.     {
  771.         return $this->innerJoin($fromAlias$join$alias$condition);
  772.     }
  773.     /**
  774.      * Creates and adds a join to the query.
  775.      *
  776.      * <code>
  777.      *     $qb = $conn->createQueryBuilder()
  778.      *         ->select('u.name')
  779.      *         ->from('users', 'u')
  780.      *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
  781.      * </code>
  782.      *
  783.      * @param string $fromAlias The alias that points to a from clause.
  784.      * @param string $join      The table name to join.
  785.      * @param string $alias     The alias of the join table.
  786.      * @param string $condition The condition for the join.
  787.      *
  788.      * @return $this This QueryBuilder instance.
  789.      */
  790.     public function innerJoin($fromAlias$join$alias$condition null)
  791.     {
  792.         return $this->add('join', [
  793.             $fromAlias => [
  794.                 'joinType'      => 'inner',
  795.                 'joinTable'     => $join,
  796.                 'joinAlias'     => $alias,
  797.                 'joinCondition' => $condition,
  798.             ],
  799.         ], true);
  800.     }
  801.     /**
  802.      * Creates and adds a left join to the query.
  803.      *
  804.      * <code>
  805.      *     $qb = $conn->createQueryBuilder()
  806.      *         ->select('u.name')
  807.      *         ->from('users', 'u')
  808.      *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
  809.      * </code>
  810.      *
  811.      * @param string $fromAlias The alias that points to a from clause.
  812.      * @param string $join      The table name to join.
  813.      * @param string $alias     The alias of the join table.
  814.      * @param string $condition The condition for the join.
  815.      *
  816.      * @return $this This QueryBuilder instance.
  817.      */
  818.     public function leftJoin($fromAlias$join$alias$condition null)
  819.     {
  820.         return $this->add('join', [
  821.             $fromAlias => [
  822.                 'joinType'      => 'left',
  823.                 'joinTable'     => $join,
  824.                 'joinAlias'     => $alias,
  825.                 'joinCondition' => $condition,
  826.             ],
  827.         ], true);
  828.     }
  829.     /**
  830.      * Creates and adds a right join to the query.
  831.      *
  832.      * <code>
  833.      *     $qb = $conn->createQueryBuilder()
  834.      *         ->select('u.name')
  835.      *         ->from('users', 'u')
  836.      *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
  837.      * </code>
  838.      *
  839.      * @param string $fromAlias The alias that points to a from clause.
  840.      * @param string $join      The table name to join.
  841.      * @param string $alias     The alias of the join table.
  842.      * @param string $condition The condition for the join.
  843.      *
  844.      * @return $this This QueryBuilder instance.
  845.      */
  846.     public function rightJoin($fromAlias$join$alias$condition null)
  847.     {
  848.         return $this->add('join', [
  849.             $fromAlias => [
  850.                 'joinType'      => 'right',
  851.                 'joinTable'     => $join,
  852.                 'joinAlias'     => $alias,
  853.                 'joinCondition' => $condition,
  854.             ],
  855.         ], true);
  856.     }
  857.     /**
  858.      * Sets a new value for a column in a bulk update query.
  859.      *
  860.      * <code>
  861.      *     $qb = $conn->createQueryBuilder()
  862.      *         ->update('counters', 'c')
  863.      *         ->set('c.value', 'c.value + 1')
  864.      *         ->where('c.id = ?');
  865.      * </code>
  866.      *
  867.      * @param string $key   The column to set.
  868.      * @param string $value The value, expression, placeholder, etc.
  869.      *
  870.      * @return $this This QueryBuilder instance.
  871.      */
  872.     public function set($key$value)
  873.     {
  874.         return $this->add('set'$key ' = ' $valuetrue);
  875.     }
  876.     /**
  877.      * Specifies one or more restrictions to the query result.
  878.      * Replaces any previously specified restrictions, if any.
  879.      *
  880.      * <code>
  881.      *     $qb = $conn->createQueryBuilder()
  882.      *         ->select('c.value')
  883.      *         ->from('counters', 'c')
  884.      *         ->where('c.id = ?');
  885.      *
  886.      *     // You can optionally programmatically build and/or expressions
  887.      *     $qb = $conn->createQueryBuilder();
  888.      *
  889.      *     $or = $qb->expr()->orx();
  890.      *     $or->add($qb->expr()->eq('c.id', 1));
  891.      *     $or->add($qb->expr()->eq('c.id', 2));
  892.      *
  893.      *     $qb->update('counters', 'c')
  894.      *         ->set('c.value', 'c.value + 1')
  895.      *         ->where($or);
  896.      * </code>
  897.      *
  898.      * @param mixed $predicates The restriction predicates.
  899.      *
  900.      * @return $this This QueryBuilder instance.
  901.      */
  902.     public function where($predicates)
  903.     {
  904.         if (! (func_num_args() === && $predicates instanceof CompositeExpression)) {
  905.             $predicates CompositeExpression::and(...func_get_args());
  906.         }
  907.         return $this->add('where'$predicates);
  908.     }
  909.     /**
  910.      * Adds one or more restrictions to the query results, forming a logical
  911.      * conjunction with any previously specified restrictions.
  912.      *
  913.      * <code>
  914.      *     $qb = $conn->createQueryBuilder()
  915.      *         ->select('u')
  916.      *         ->from('users', 'u')
  917.      *         ->where('u.username LIKE ?')
  918.      *         ->andWhere('u.is_active = 1');
  919.      * </code>
  920.      *
  921.      * @see where()
  922.      *
  923.      * @param mixed $where The query restrictions.
  924.      *
  925.      * @return $this This QueryBuilder instance.
  926.      */
  927.     public function andWhere($where)
  928.     {
  929.         $args  func_get_args();
  930.         $where $this->getQueryPart('where');
  931.         if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
  932.             $where $where->with(...$args);
  933.         } else {
  934.             array_unshift($args$where);
  935.             $where CompositeExpression::and(...$args);
  936.         }
  937.         return $this->add('where'$wheretrue);
  938.     }
  939.     /**
  940.      * Adds one or more restrictions to the query results, forming a logical
  941.      * disjunction with any previously specified restrictions.
  942.      *
  943.      * <code>
  944.      *     $qb = $em->createQueryBuilder()
  945.      *         ->select('u.name')
  946.      *         ->from('users', 'u')
  947.      *         ->where('u.id = 1')
  948.      *         ->orWhere('u.id = 2');
  949.      * </code>
  950.      *
  951.      * @see where()
  952.      *
  953.      * @param mixed $where The WHERE statement.
  954.      *
  955.      * @return $this This QueryBuilder instance.
  956.      */
  957.     public function orWhere($where)
  958.     {
  959.         $args  func_get_args();
  960.         $where $this->getQueryPart('where');
  961.         if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
  962.             $where $where->with(...$args);
  963.         } else {
  964.             array_unshift($args$where);
  965.             $where CompositeExpression::or(...$args);
  966.         }
  967.         return $this->add('where'$wheretrue);
  968.     }
  969.     /**
  970.      * Specifies a grouping over the results of the query.
  971.      * Replaces any previously specified groupings, if any.
  972.      *
  973.      * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument.
  974.      *
  975.      * <code>
  976.      *     $qb = $conn->createQueryBuilder()
  977.      *         ->select('u.name')
  978.      *         ->from('users', 'u')
  979.      *         ->groupBy('u.id');
  980.      * </code>
  981.      *
  982.      * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED.
  983.      *                                 Pass each value as an individual argument.
  984.      *
  985.      * @return $this This QueryBuilder instance.
  986.      */
  987.     public function groupBy($groupBy/*, string ...$groupBys*/)
  988.     {
  989.         if (is_array($groupBy) && count($groupBy) === 0) {
  990.             return $this;
  991.         }
  992.         if (is_array($groupBy)) {
  993.             Deprecation::trigger(
  994.                 'doctrine/dbal',
  995.                 'https://github.com/doctrine/dbal/issues/3837',
  996.                 'Passing an array for the first argument to QueryBuilder::groupBy() is deprecated, ' .
  997.                 'pass each value as an individual variadic argument instead.',
  998.             );
  999.         }
  1000.         $groupBy is_array($groupBy) ? $groupBy func_get_args();
  1001.         return $this->add('groupBy'$groupByfalse);
  1002.     }
  1003.     /**
  1004.      * Adds a grouping expression to the query.
  1005.      *
  1006.      * USING AN ARRAY ARGUMENT IS DEPRECATED. Pass each value as an individual argument.
  1007.      *
  1008.      * <code>
  1009.      *     $qb = $conn->createQueryBuilder()
  1010.      *         ->select('u.name')
  1011.      *         ->from('users', 'u')
  1012.      *         ->groupBy('u.lastLogin')
  1013.      *         ->addGroupBy('u.createdAt');
  1014.      * </code>
  1015.      *
  1016.      * @param string|string[] $groupBy The grouping expression. USING AN ARRAY IS DEPRECATED.
  1017.      *                                 Pass each value as an individual argument.
  1018.      *
  1019.      * @return $this This QueryBuilder instance.
  1020.      */
  1021.     public function addGroupBy($groupBy/*, string ...$groupBys*/)
  1022.     {
  1023.         if (is_array($groupBy) && count($groupBy) === 0) {
  1024.             return $this;
  1025.         }
  1026.         if (is_array($groupBy)) {
  1027.             Deprecation::trigger(
  1028.                 'doctrine/dbal',
  1029.                 'https://github.com/doctrine/dbal/issues/3837',
  1030.                 'Passing an array for the first argument to QueryBuilder::addGroupBy() is deprecated, ' .
  1031.                 'pass each value as an individual variadic argument instead.',
  1032.             );
  1033.         }
  1034.         $groupBy is_array($groupBy) ? $groupBy func_get_args();
  1035.         return $this->add('groupBy'$groupBytrue);
  1036.     }
  1037.     /**
  1038.      * Sets a value for a column in an insert query.
  1039.      *
  1040.      * <code>
  1041.      *     $qb = $conn->createQueryBuilder()
  1042.      *         ->insert('users')
  1043.      *         ->values(
  1044.      *             array(
  1045.      *                 'name' => '?'
  1046.      *             )
  1047.      *         )
  1048.      *         ->setValue('password', '?');
  1049.      * </code>
  1050.      *
  1051.      * @param string $column The column into which the value should be inserted.
  1052.      * @param string $value  The value that should be inserted into the column.
  1053.      *
  1054.      * @return $this This QueryBuilder instance.
  1055.      */
  1056.     public function setValue($column$value)
  1057.     {
  1058.         $this->sqlParts['values'][$column] = $value;
  1059.         return $this;
  1060.     }
  1061.     /**
  1062.      * Specifies values for an insert query indexed by column names.
  1063.      * Replaces any previous values, if any.
  1064.      *
  1065.      * <code>
  1066.      *     $qb = $conn->createQueryBuilder()
  1067.      *         ->insert('users')
  1068.      *         ->values(
  1069.      *             array(
  1070.      *                 'name' => '?',
  1071.      *                 'password' => '?'
  1072.      *             )
  1073.      *         );
  1074.      * </code>
  1075.      *
  1076.      * @param mixed[] $values The values to specify for the insert query indexed by column names.
  1077.      *
  1078.      * @return $this This QueryBuilder instance.
  1079.      */
  1080.     public function values(array $values)
  1081.     {
  1082.         return $this->add('values'$values);
  1083.     }
  1084.     /**
  1085.      * Specifies a restriction over the groups of the query.
  1086.      * Replaces any previous having restrictions, if any.
  1087.      *
  1088.      * @param mixed $having The restriction over the groups.
  1089.      *
  1090.      * @return $this This QueryBuilder instance.
  1091.      */
  1092.     public function having($having)
  1093.     {
  1094.         if (! (func_num_args() === && $having instanceof CompositeExpression)) {
  1095.             $having CompositeExpression::and(...func_get_args());
  1096.         }
  1097.         return $this->add('having'$having);
  1098.     }
  1099.     /**
  1100.      * Adds a restriction over the groups of the query, forming a logical
  1101.      * conjunction with any existing having restrictions.
  1102.      *
  1103.      * @param mixed $having The restriction to append.
  1104.      *
  1105.      * @return $this This QueryBuilder instance.
  1106.      */
  1107.     public function andHaving($having)
  1108.     {
  1109.         $args   func_get_args();
  1110.         $having $this->getQueryPart('having');
  1111.         if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
  1112.             $having $having->with(...$args);
  1113.         } else {
  1114.             array_unshift($args$having);
  1115.             $having CompositeExpression::and(...$args);
  1116.         }
  1117.         return $this->add('having'$having);
  1118.     }
  1119.     /**
  1120.      * Adds a restriction over the groups of the query, forming a logical
  1121.      * disjunction with any existing having restrictions.
  1122.      *
  1123.      * @param mixed $having The restriction to add.
  1124.      *
  1125.      * @return $this This QueryBuilder instance.
  1126.      */
  1127.     public function orHaving($having)
  1128.     {
  1129.         $args   func_get_args();
  1130.         $having $this->getQueryPart('having');
  1131.         if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
  1132.             $having $having->with(...$args);
  1133.         } else {
  1134.             array_unshift($args$having);
  1135.             $having CompositeExpression::or(...$args);
  1136.         }
  1137.         return $this->add('having'$having);
  1138.     }
  1139.     /**
  1140.      * Specifies an ordering for the query results.
  1141.      * Replaces any previously specified orderings, if any.
  1142.      *
  1143.      * @param string $sort  The ordering expression.
  1144.      * @param string $order The ordering direction.
  1145.      *
  1146.      * @return $this This QueryBuilder instance.
  1147.      */
  1148.     public function orderBy($sort$order null)
  1149.     {
  1150.         return $this->add('orderBy'$sort ' ' . ($order ?? 'ASC'), false);
  1151.     }
  1152.     /**
  1153.      * Adds an ordering to the query results.
  1154.      *
  1155.      * @param string $sort  The ordering expression.
  1156.      * @param string $order The ordering direction.
  1157.      *
  1158.      * @return $this This QueryBuilder instance.
  1159.      */
  1160.     public function addOrderBy($sort$order null)
  1161.     {
  1162.         return $this->add('orderBy'$sort ' ' . ($order ?? 'ASC'), true);
  1163.     }
  1164.     /**
  1165.      * Gets a query part by its name.
  1166.      *
  1167.      * @param string $queryPartName
  1168.      *
  1169.      * @return mixed
  1170.      */
  1171.     public function getQueryPart($queryPartName)
  1172.     {
  1173.         return $this->sqlParts[$queryPartName];
  1174.     }
  1175.     /**
  1176.      * Gets all query parts.
  1177.      *
  1178.      * @return mixed[]
  1179.      */
  1180.     public function getQueryParts()
  1181.     {
  1182.         return $this->sqlParts;
  1183.     }
  1184.     /**
  1185.      * Resets SQL parts.
  1186.      *
  1187.      * @param string[]|null $queryPartNames
  1188.      *
  1189.      * @return $this This QueryBuilder instance.
  1190.      */
  1191.     public function resetQueryParts($queryPartNames null)
  1192.     {
  1193.         $queryPartNames ??= array_keys($this->sqlParts);
  1194.         foreach ($queryPartNames as $queryPartName) {
  1195.             $this->resetQueryPart($queryPartName);
  1196.         }
  1197.         return $this;
  1198.     }
  1199.     /**
  1200.      * Resets a single SQL part.
  1201.      *
  1202.      * @param string $queryPartName
  1203.      *
  1204.      * @return $this This QueryBuilder instance.
  1205.      */
  1206.     public function resetQueryPart($queryPartName)
  1207.     {
  1208.         $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName];
  1209.         $this->state self::STATE_DIRTY;
  1210.         return $this;
  1211.     }
  1212.     /** @throws QueryException */
  1213.     private function getSQLForSelect(): string
  1214.     {
  1215.         $query 'SELECT ' . ($this->sqlParts['distinct'] ? 'DISTINCT ' '') .
  1216.                   implode(', '$this->sqlParts['select']);
  1217.         $query .= ($this->sqlParts['from'] ? ' FROM ' implode(', '$this->getFromClauses()) : '')
  1218.             . ($this->sqlParts['where'] !== null ' WHERE ' . ((string) $this->sqlParts['where']) : '')
  1219.             . ($this->sqlParts['groupBy'] ? ' GROUP BY ' implode(', '$this->sqlParts['groupBy']) : '')
  1220.             . ($this->sqlParts['having'] !== null ' HAVING ' . ((string) $this->sqlParts['having']) : '')
  1221.             . ($this->sqlParts['orderBy'] ? ' ORDER BY ' implode(', '$this->sqlParts['orderBy']) : '');
  1222.         if ($this->isLimitQuery()) {
  1223.             return $this->connection->getDatabasePlatform()->modifyLimitQuery(
  1224.                 $query,
  1225.                 $this->maxResults,
  1226.                 $this->firstResult,
  1227.             );
  1228.         }
  1229.         return $query;
  1230.     }
  1231.     /**
  1232.      * @return string[]
  1233.      *
  1234.      * @throws QueryException
  1235.      */
  1236.     private function getFromClauses(): array
  1237.     {
  1238.         $fromClauses  = [];
  1239.         $knownAliases = [];
  1240.         // Loop through all FROM clauses
  1241.         foreach ($this->sqlParts['from'] as $from) {
  1242.             if ($from['alias'] === null) {
  1243.                 $tableSql       $from['table'];
  1244.                 $tableReference $from['table'];
  1245.             } else {
  1246.                 $tableSql       $from['table'] . ' ' $from['alias'];
  1247.                 $tableReference $from['alias'];
  1248.             }
  1249.             $knownAliases[$tableReference] = true;
  1250.             $fromClauses[$tableReference] = $tableSql $this->getSQLForJoins($tableReference$knownAliases);
  1251.         }
  1252.         $this->verifyAllAliasesAreKnown($knownAliases);
  1253.         return $fromClauses;
  1254.     }
  1255.     /**
  1256.      * @param array<string,true> $knownAliases
  1257.      *
  1258.      * @throws QueryException
  1259.      */
  1260.     private function verifyAllAliasesAreKnown(array $knownAliases): void
  1261.     {
  1262.         foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
  1263.             if (! isset($knownAliases[$fromAlias])) {
  1264.                 throw QueryException::unknownAlias($fromAliasarray_keys($knownAliases));
  1265.             }
  1266.         }
  1267.     }
  1268.     private function isLimitQuery(): bool
  1269.     {
  1270.         return $this->maxResults !== null || $this->firstResult !== 0;
  1271.     }
  1272.     /**
  1273.      * Converts this instance into an INSERT string in SQL.
  1274.      */
  1275.     private function getSQLForInsert(): string
  1276.     {
  1277.         return 'INSERT INTO ' $this->sqlParts['from']['table'] .
  1278.         ' (' implode(', 'array_keys($this->sqlParts['values'])) . ')' .
  1279.         ' VALUES(' implode(', '$this->sqlParts['values']) . ')';
  1280.     }
  1281.     /**
  1282.      * Converts this instance into an UPDATE string in SQL.
  1283.      */
  1284.     private function getSQLForUpdate(): string
  1285.     {
  1286.         $table $this->sqlParts['from']['table']
  1287.             . ($this->sqlParts['from']['alias'] ? ' ' $this->sqlParts['from']['alias'] : '');
  1288.         return 'UPDATE ' $table
  1289.             ' SET ' implode(', '$this->sqlParts['set'])
  1290.             . ($this->sqlParts['where'] !== null ' WHERE ' . ((string) $this->sqlParts['where']) : '');
  1291.     }
  1292.     /**
  1293.      * Converts this instance into a DELETE string in SQL.
  1294.      */
  1295.     private function getSQLForDelete(): string
  1296.     {
  1297.         $table $this->sqlParts['from']['table']
  1298.             . ($this->sqlParts['from']['alias'] ? ' ' $this->sqlParts['from']['alias'] : '');
  1299.         return 'DELETE FROM ' $table
  1300.             . ($this->sqlParts['where'] !== null ' WHERE ' . ((string) $this->sqlParts['where']) : '');
  1301.     }
  1302.     /**
  1303.      * Gets a string representation of this QueryBuilder which corresponds to
  1304.      * the final SQL query being constructed.
  1305.      *
  1306.      * @return string The string representation of this QueryBuilder.
  1307.      */
  1308.     public function __toString()
  1309.     {
  1310.         return $this->getSQL();
  1311.     }
  1312.     /**
  1313.      * Creates a new named parameter and bind the value $value to it.
  1314.      *
  1315.      * This method provides a shortcut for {@see Statement::bindValue()}
  1316.      * when using prepared statements.
  1317.      *
  1318.      * The parameter $value specifies the value that you want to bind. If
  1319.      * $placeholder is not provided createNamedParameter() will automatically
  1320.      * create a placeholder for you. An automatic placeholder will be of the
  1321.      * name ':dcValue1', ':dcValue2' etc.
  1322.      *
  1323.      * Example:
  1324.      * <code>
  1325.      * $value = 2;
  1326.      * $q->eq( 'id', $q->createNamedParameter( $value ) );
  1327.      * $stmt = $q->executeQuery(); // executed with 'id = 2'
  1328.      * </code>
  1329.      *
  1330.      * @link http://www.zetacomponents.org
  1331.      *
  1332.      * @param mixed                $value
  1333.      * @param int|string|Type|null $type
  1334.      * @param string               $placeHolder The name to bind with. The string must start with a colon ':'.
  1335.      *
  1336.      * @return string the placeholder name used.
  1337.      */
  1338.     public function createNamedParameter($value$type ParameterType::STRING$placeHolder null)
  1339.     {
  1340.         if ($placeHolder === null) {
  1341.             $this->boundCounter++;
  1342.             $placeHolder ':dcValue' $this->boundCounter;
  1343.         }
  1344.         $this->setParameter(substr($placeHolder1), $value$type);
  1345.         return $placeHolder;
  1346.     }
  1347.     /**
  1348.      * Creates a new positional parameter and bind the given value to it.
  1349.      *
  1350.      * Attention: If you are using positional parameters with the query builder you have
  1351.      * to be very careful to bind all parameters in the order they appear in the SQL
  1352.      * statement , otherwise they get bound in the wrong order which can lead to serious
  1353.      * bugs in your code.
  1354.      *
  1355.      * Example:
  1356.      * <code>
  1357.      *  $qb = $conn->createQueryBuilder();
  1358.      *  $qb->select('u.*')
  1359.      *     ->from('users', 'u')
  1360.      *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
  1361.      *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
  1362.      * </code>
  1363.      *
  1364.      * @param mixed                $value
  1365.      * @param int|string|Type|null $type
  1366.      *
  1367.      * @return string
  1368.      */
  1369.     public function createPositionalParameter($value$type ParameterType::STRING)
  1370.     {
  1371.         $this->setParameter($this->boundCounter$value$type);
  1372.         $this->boundCounter++;
  1373.         return '?';
  1374.     }
  1375.     /**
  1376.      * @param string             $fromAlias
  1377.      * @param array<string,true> $knownAliases
  1378.      *
  1379.      * @throws QueryException
  1380.      */
  1381.     private function getSQLForJoins($fromAlias, array &$knownAliases): string
  1382.     {
  1383.         $sql '';
  1384.         if (isset($this->sqlParts['join'][$fromAlias])) {
  1385.             foreach ($this->sqlParts['join'][$fromAlias] as $join) {
  1386.                 if (array_key_exists($join['joinAlias'], $knownAliases)) {
  1387.                     throw QueryException::nonUniqueAlias((string) $join['joinAlias'], array_keys($knownAliases));
  1388.                 }
  1389.                 $sql .= ' ' strtoupper($join['joinType'])
  1390.                     . ' JOIN ' $join['joinTable'] . ' ' $join['joinAlias'];
  1391.                 if ($join['joinCondition'] !== null) {
  1392.                     $sql .= ' ON ' $join['joinCondition'];
  1393.                 }
  1394.                 $knownAliases[$join['joinAlias']] = true;
  1395.             }
  1396.             foreach ($this->sqlParts['join'][$fromAlias] as $join) {
  1397.                 $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
  1398.             }
  1399.         }
  1400.         return $sql;
  1401.     }
  1402.     /**
  1403.      * Deep clone of all expression objects in the SQL parts.
  1404.      *
  1405.      * @return void
  1406.      */
  1407.     public function __clone()
  1408.     {
  1409.         foreach ($this->sqlParts as $part => $elements) {
  1410.             if (is_array($this->sqlParts[$part])) {
  1411.                 foreach ($this->sqlParts[$part] as $idx => $element) {
  1412.                     if (! is_object($element)) {
  1413.                         continue;
  1414.                     }
  1415.                     $this->sqlParts[$part][$idx] = clone $element;
  1416.                 }
  1417.             } elseif (is_object($elements)) {
  1418.                 $this->sqlParts[$part] = clone $elements;
  1419.             }
  1420.         }
  1421.         foreach ($this->params as $name => $param) {
  1422.             if (! is_object($param)) {
  1423.                 continue;
  1424.             }
  1425.             $this->params[$name] = clone $param;
  1426.         }
  1427.     }
  1428.     /**
  1429.      * Enables caching of the results of this query, for given amount of seconds
  1430.      * and optionally specified witch key to use for the cache entry.
  1431.      *
  1432.      * @return $this
  1433.      */
  1434.     public function enableResultCache(QueryCacheProfile $cacheProfile): self
  1435.     {
  1436.         $this->resultCacheProfile $cacheProfile;
  1437.         return $this;
  1438.     }
  1439.     /**
  1440.      * Disables caching of the results of this query.
  1441.      *
  1442.      * @return $this
  1443.      */
  1444.     public function disableResultCache(): self
  1445.     {
  1446.         $this->resultCacheProfile null;
  1447.         return $this;
  1448.     }
  1449. }