Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/includes/libs/rdbms/querybuilder/SelectQueryBuilder.php
Ðазад
<?php namespace Wikimedia\Rdbms; use Wikimedia\Rdbms\Platform\ISQLPlatform; // Very long type annotations :( // phpcs:disable Generic.Files.LineLength /** * Build SELECT queries with a fluent interface. * * Each query builder object must be used for a single database query only, * and not be reused afterwards. To run multiple similar queries, you can * create a query builder to set up most of your query, which you can use * as a "template" to clone. You can then modify the cloned object for * each individual query. * * Note that the methods in this class are not stable to override. * This class may be extended to create query builders for specific database * tables, such {@link \MediaWiki\Page\PageSelectQueryBuilder}, whilst still * providing the same fluent interface for adding arbitrary additional * conditions and such. * * @since 1.35 * @stable to extend * @ingroup Database */ class SelectQueryBuilder extends JoinGroupBase { /** sort the results in ascending order */ public const SORT_ASC = 'ASC'; /** sort the results in descending order */ public const SORT_DESC = 'DESC'; /** * @var array The fields to be passed to IReadableDatabase::select() */ private $fields = []; /** * @var array The conditions to be passed to IReadableDatabase::select() */ private $conds = []; /** * @var string The caller (function name) to be passed to IReadableDatabase::select() */ private $caller = __CLASS__; /** * @var array The options to be passed to IReadableDatabase::select() */ protected $options = []; /** * @var int An integer used to assign automatic aliases to tables and groups */ private $nextAutoAlias = 1; /** * @var bool True if $this->caller has been set */ private $isCallerOverridden = false; /** @var IReadableDatabase */ protected IReadableDatabase $db; /** * Only for use in subclasses. To create a SelectQueryBuilder instance, * use `$db->newSelectQueryBuilder()` instead. * * @param IReadableDatabase $db */ public function __construct( IReadableDatabase $db ) { $this->db = $db; } /** * Change the IReadableDatabase object the query builder is bound to. The specified * IReadableDatabase will subsequently be used to execute the query. * * @param IReadableDatabase $db * @return $this */ public function connection( IReadableDatabase $db ) { if ( $this->db->getType() !== $db->getType() ) { throw new \InvalidArgumentException( __METHOD__ . ' cannot switch to a database of a different type.' ); } $this->db = $db; return $this; } /** * Set the query parameters to the given values, appending to the values * which were already set. This can be used to interface with legacy code. * If a key is omitted, the previous value will be retained. * * The parameters must be formatted as required by IReadableDatabase::select. For * example, JoinGroup cannot be used. * * @param array $info Associative array of query info, with keys: * - tables: The raw array of tables to be passed to IReadableDatabase::select() * - fields: The fields * - conds: The conditions * - options: The query options * - join_conds: The join conditions * - joins: Alias for join_conds. If both joins and join_conds are * specified, the values will be merged. * - caller: The caller signature * * @return $this */ public function queryInfo( $info ) { if ( isset( $info['tables'] ) ) { $this->rawTables( $info['tables'] ); } if ( isset( $info['fields'] ) ) { $this->fields( $info['fields'] ); } if ( isset( $info['conds'] ) ) { $this->where( $info['conds'] ); } if ( isset( $info['options'] ) ) { $this->options( (array)$info['options'] ); } if ( isset( $info['join_conds'] ) ) { $this->joinConds( (array)$info['join_conds'] ); } if ( isset( $info['joins'] ) ) { $this->joinConds( (array)$info['joins'] ); } if ( isset( $info['caller'] ) ) { $this->caller( $info['caller'] ); } return $this; } /** * Given a table or table array as might be passed to IReadableDatabase::select(), * append it to the existing tables, interpreting nested arrays as join * groups. * * This can be used to interface with existing code that expresses join * groups as nested arrays. In new code, join groups should generally * be created with newJoinGroup(), which provides a fluent interface. * * @param string|array $tables Table references; see {@link IReadableDatabase::select} * for details * @return $this */ public function rawTables( $tables ) { if ( is_array( $tables ) ) { $this->tables = array_merge( $this->tables, $tables ); } elseif ( is_string( $tables ) ) { $this->tables[] = $tables; } else { throw new \InvalidArgumentException( __METHOD__ . ': $tables must be a string or array' ); } return $this; } /** * Merge another query builder with this one. Append the other builder's * tables, joins, fields, conditions and options to this one. * * @since 1.41 * @param SelectQueryBuilder $builder * @return $this */ public function merge( SelectQueryBuilder $builder ) { $this->rawTables( $builder->tables ); $this->fields( $builder->fields ); $this->where( $builder->conds ); $this->options( $builder->options ); $this->joinConds( $builder->joinConds ); if ( $builder->isCallerOverridden ) { $this->caller( $builder->caller ); } return $this; } /** * Get an empty SelectQueryBuilder which can be used to build a subquery * of this query. * @return SelectQueryBuilder */ public function newSubquery() { return new self( $this->db ); } /** * Add a single table to the SELECT query. Alias for table(). * * @param string|JoinGroup|SelectQueryBuilder $table Table reference; see {@link table} * for details * @param-taint $table exec_sql * @param string|null $alias The table alias, or null for no alias * @param-taint $alias exec_sql * @return $this */ public function from( $table, $alias = null ) { return $this->table( $table, $alias ); } /** * Add multiple tables. It's recommended to use join() and leftJoin() instead in new code. * * @param string[] $tables Table references (string keys are aliases). See {@link table} * for details. * @param-taint $tables exec_sql * @return $this */ public function tables( $tables ) { foreach ( $tables as $alias => $table ) { if ( is_string( $alias ) ) { $this->table( $table, $alias ); } else { $this->table( $table ); } } return $this; } /** * Add a field or an array of fields to the query. Each field is an SQL * fragment. If the array key is non-numeric, the key is taken to be an * alias for the field. * * @see IReadableDatabase::select() * * @param string|string[] $fields * @param-taint $fields exec_sql * @return $this */ public function fields( $fields ) { if ( is_array( $fields ) ) { $this->fields = array_merge( $this->fields, $fields ); } else { $this->fields[] = $fields; } return $this; } /** * Add a field or an array of fields to the query. Alias for fields(). * * @param string|string[] $fields * @param-taint $fields exec_sql * @return $this */ public function select( $fields ) { return $this->fields( $fields ); } /** * Add a single field to the query, optionally with an alias. The field is * an SQL fragment. It is unsafe to pass user input to this function. * * @param string $field * @param-taint $field exec_sql * @param string|null $alias * @param-taint $alias exec_sql * @return $this */ public function field( $field, $alias = null ) { if ( $alias === null ) { $this->fields[] = $field; } else { $this->fields[$alias] = $field; } return $this; } /** * Remove all fields from the query. * * @return $this */ public function clearFields() { $this->fields = []; return $this; } /** * Add conditions to the query. The supplied conditions will be appended * to the existing conditions, separated by AND. * * @param string|IExpression|array<string,?scalar|non-empty-array<int,?scalar>|RawSQLValue>|array<int,string|IExpression> $conds * @param-taint $conds exec_sql_numkey * * May be either a string containing a single condition, or an array of * conditions. If an array is given, the conditions constructed from each * element are combined with AND. * * Array elements may take one of two forms: * * - Elements with a numeric key are interpreted as raw SQL fragments. * - Elements with a string key are interpreted as equality conditions, * where the key is the field name. * - If the value of such an array element is a scalar (such as a * string), it will be treated as data and thus quoted appropriately. * If it is null, an IS NULL clause will be added. * - If the value is an array, an IN (...) clause will be constructed * from its non-null elements, and an IS NULL clause will be added * if null is present, such that the field may match any of the * elements in the array. The non-null elements will be quoted. * * Note that expressions are often DBMS-dependent in their syntax. * DBMS-independent wrappers are provided for constructing several types of * expression commonly used in condition queries. See: * - IReadableDatabase::buildLike() * - IReadableDatabase::conditional() * * Untrusted user input is safe in the values of string keys, however untrusted * input must not be used in the array key names or in the values of numeric keys. * Escaping of untrusted input used in values of numeric keys should be done via * IReadableDatabase::addQuotes() * * @return $this */ public function where( $conds ) { if ( is_array( $conds ) ) { foreach ( $conds as $key => $cond ) { if ( is_int( $key ) ) { $this->conds[] = $cond; } elseif ( isset( $this->conds[$key] ) ) { // @phan-suppress-previous-line PhanTypeMismatchDimFetch // T288882 $this->conds[] = $this->db->makeList( [ $key => $cond ], IReadableDatabase::LIST_AND ); } else { $this->conds[$key] = $cond; } } } else { $this->conds[] = $conds; } return $this; } /** * Add conditions to the query. Alias for where(). * * @param string|IExpression|array<string,?scalar|non-empty-array<int,?scalar>|RawSQLValue>|array<int,string|IExpression> $conds * @param-taint $conds exec_sql_numkey * @return $this */ public function andWhere( $conds ) { return $this->where( $conds ); } /** * Add conditions to the query. Alias for where(). * * @param string|IExpression|array<string,?scalar|non-empty-array<int,?scalar>|RawSQLValue>|array<int,string|IExpression> $conds * @param-taint $conds exec_sql_numkey * @return $this */ public function conds( $conds ) { return $this->where( $conds ); } /** * Manually append to the $join_conds array which will be passed to * IReadableDatabase::select(). This is not recommended for new code. Instead, * join() and leftJoin() should be used. * * @param array $joinConds * @return $this */ public function joinConds( array $joinConds ) { $this->joinConds = array_merge( $this->joinConds, $joinConds ); return $this; } /** * Get a table alias which is unique to this SelectQueryBuilder * * @return string */ protected function getAutoAlias() { return 'sqb' . ( $this->nextAutoAlias++ ); } /** * Create a parenthesized group of joins which can be added to the object * like a table. The group is initially empty. * * @return JoinGroup */ public function newJoinGroup() { return new JoinGroup( $this->getAutoAlias() ); } /** * Set the offset. Skip this many rows at the start of the result set. Offset * with limit() can theoretically be used for paging through a result set, * but this is discouraged for performance reasons. * * If the query builder already has an offset, the old offset will be discarded. * * @param int $offset * @return $this */ public function offset( $offset ) { $this->options['OFFSET'] = $offset; return $this; } /** * Set the query limit. Return at most this many rows. The rows are sorted * and then the first rows are taken until the limit is reached. Limit * is applied to a result set after offset. * * If the query builder already has a limit, the old limit will be discarded. * * @param int $limit * @return $this */ public function limit( $limit ) { $this->options['LIMIT'] = $limit; return $this; } /** * Enable the LOCK IN SHARE MODE option. Lock the returned rows so that * they can't be changed until the next COMMIT. Cannot be used with * aggregate functions (COUNT, MAX, etc., but also DISTINCT). * * @return $this */ public function lockInShareMode() { $this->options[] = 'LOCK IN SHARE MODE'; return $this; } /** * Enable the FOR UPDATE option. Lock the returned rows so that * they can't be changed until the next COMMIT. Cannot be used with * aggregate functions (COUNT, MAX, etc., but also DISTINCT). * * @return $this */ public function forUpdate() { $this->options[] = 'FOR UPDATE'; return $this; } /** * Enable the DISTINCT option. Return only unique result rows. * * @return $this */ public function distinct() { $this->options[] = 'DISTINCT'; return $this; } /** * Set MAX_EXECUTION_TIME for queries. * * @param int $time maximum allowed time in milliseconds * @return $this */ public function setMaxExecutionTime( int $time ) { $this->options['MAX_EXECUTION_TIME'] = $time; return $this; } /** * Add a GROUP BY clause. May be either an SQL fragment string naming a * field or expression to group by, or an array of such SQL fragments. * * If there is an existing GROUP BY clause, the new one will be appended. * * @param string|string[] $group * @param-taint $group exec_sql * @return $this */ public function groupBy( $group ) { $this->mergeOption( 'GROUP BY', $group ); return $this; } /** * Add a HAVING clause. May be either a string containing a HAVING clause * or an array of conditions building the HAVING clause. If an array is * given, the conditions constructed from each element are combined with * AND. * * If there is an existing HAVING clause, the new one will be appended. * * @param string|string[] $having * @param-taint $having exec_sql_numkey * @return $this */ public function having( $having ) { $this->mergeOption( 'HAVING', $having ); return $this; } /** * Set the ORDER BY clause. If it has already been set, append the * additional fields to it. * * @param string[]|string $fields The field or list of fields to order by. * @param-taint $fields exec_sql * @param string|null $direction Sorting direction applied to all fields, * self::SORT_ASC or self::SORT_DESC. If different fields need to be sorted in opposite * directions, then this parameter must be omitted, and $fields must contain 'ASC' or 'DESC' * after each field name. * @param-taint $direction exec_sql * @return $this */ public function orderBy( $fields, $direction = null ) { if ( $direction === null ) { $this->mergeOption( 'ORDER BY', $fields ); } elseif ( is_array( $fields ) ) { $fieldsWithDirection = []; foreach ( $fields as $field ) { $fieldsWithDirection[] = "$field $direction"; } $this->mergeOption( 'ORDER BY', $fieldsWithDirection ); } else { $this->mergeOption( 'ORDER BY', "$fields $direction" ); } return $this; } /** * Add a value to an option which may be not set or a string or array. * * @param string $name * @param string|string[] $newArrayOrValue */ private function mergeOption( $name, $newArrayOrValue ) { $value = isset( $this->options[$name] ) ? (array)$this->options[$name] : []; if ( is_array( $newArrayOrValue ) ) { $value = array_merge( $value, $newArrayOrValue ); } else { $value[] = $newArrayOrValue; } $this->options[$name] = $value; } /** * Set a USE INDEX option. * * If a string is given, the index hint is applied to the most recently * appended table or alias. If an array is given, it is assumed to be an * associative array with the alias names in the keys and the indexes in * the values, as in the USE INDEX option to IReadableDatabase::select(). The * array will be merged with the existing value. * * @param string|string[] $index * @param-taint $index exec_sql * @return $this */ public function useIndex( $index ) { $this->setIndexHint( 'USE INDEX', $index ); return $this; } /** * Set the IGNORE INDEX option. * * If a string is given, the index hint is applied to the most recently * appended table or alias. If an array is given, it is assumed to be an * associative array with the alias names in the keys and the indexes in * the values, as in the IGNORE INDEX option to IReadableDatabase::select(). The * array will be merged with the existing value. * * @param string|string[] $index * @param-taint $index exec_sql * @return $this */ public function ignoreIndex( $index ) { $this->setIndexHint( 'IGNORE INDEX', $index ); return $this; } /** * Private helper for methods that set index hints. * * @param string $type * @param string|string[] $value */ private function setIndexHint( $type, $value ) { if ( !isset( $this->options[$type] ) ) { $this->options[$type] = []; } elseif ( !is_array( $this->options[$type] ) ) { throw new \UnexpectedValueException( __METHOD__ . ": The $type option cannot be appended to " . 'because it is not an array. This may have been caused by a prior ' . 'call to option() or options().' ); } if ( is_array( $value ) ) { $this->options[$type] = array_merge( $this->options[$type], $value ); } elseif ( $this->lastAlias === null ) { throw new \UnexpectedValueException( __METHOD__ . ': Cannot append index value since there is no' . 'prior table' ); } else { $this->options[$type][$this->lastAlias] = $value; } } /** * Make the query be an EXPLAIN SELECT query instead of a SELECT query. * * @return $this */ public function explain() { $this->options['EXPLAIN'] = true; return $this; } /** * Enable the STRAIGHT_JOIN query option. * * @return $this */ public function straightJoinOption() { $this->options[] = 'STRAIGHT_JOIN'; return $this; } /** * Enable the SQL_BIG_RESULT option. * * @return $this */ public function bigResult() { $this->options[] = 'SQL_BIG_RESULT'; return $this; } /** * Enable the SQL_BUFFER_RESULT option. * * @return $this */ public function bufferResult() { $this->options[] = 'SQL_BUFFER_RESULT'; return $this; } /** * Enable the SQL_SMALL_RESULT option. * * @return $this */ public function smallResult() { $this->options[] = 'SQL_SMALL_RESULT'; return $this; } /** * Enable the SQL_CALC_FOUND_ROWS option. * * @return $this */ public function calcFoundRows() { $this->options[] = 'SQL_CALC_FOUND_ROWS'; return $this; } /** * Manually set an option in the $options array to be passed to * IReadableDatabase::select() * * @param string $name The option name * @param mixed $value The option value, or null for a boolean option * @return $this */ public function option( $name, $value = null ) { if ( $value === null ) { $this->options[] = $name; } else { $this->options[$name] = $value; } return $this; } /** * Manually set multiple options in the $options array to be passed to * IReadableDatabase::select(). * * @param array $options * @return $this */ public function options( array $options ) { $this->options = array_merge( $this->options, $options ); return $this; } /** * @param int $recency Bitfield of IDBAccessObject::READ_* constants * @return $this */ public function recency( $recency ) { if ( ( $recency & IDBAccessObject::READ_EXCLUSIVE ) == IDBAccessObject::READ_EXCLUSIVE ) { $this->forUpdate(); } elseif ( ( $recency & IDBAccessObject::READ_LOCKING ) == IDBAccessObject::READ_LOCKING ) { $this->lockInShareMode(); } return $this; } /** * Set the method name to be included in an SQL comment. * * @param string $fname * @param-taint $fname exec_sql * @return $this */ public function caller( $fname ) { $this->caller = $fname; $this->isCallerOverridden = true; return $this; } /** * get the method name of the caller, for use in sub classes * * @since 1.43 */ final protected function getCaller(): string { return $this->caller; } /** * Run the constructed SELECT query and return all results. * * @return IResultWrapper * @return-taint tainted */ public function fetchResultSet(): IResultWrapper { return $this->db->select( $this->tables, $this->fields, $this->conds, $this->caller, $this->options, $this->joinConds ); } /** * Run the constructed SELECT query, and return a single field extracted * from the first result row. This may only be called when only one field * has been added to the builder. * * @return mixed|false The value from the field, or false if nothing was found * @return-taint tainted */ public function fetchField() { if ( count( $this->fields ) !== 1 ) { throw new \UnexpectedValueException( __METHOD__ . ' expects the query to have only one field' ); } $field = reset( $this->fields ); return $this->db->selectField( $this->tables, $field, $this->conds, $this->caller, $this->options, $this->joinConds ); } /** * Run the constructed SELECT query, and extract a single field from each * result row, returning an array containing all the values. This may only * be called when only one field has been added to the builder. * * @return array * @return-taint tainted */ public function fetchFieldValues(): array { if ( count( $this->fields ) !== 1 ) { throw new \UnexpectedValueException( __METHOD__ . ' expects the query to have only one field' ); } $field = reset( $this->fields ); return $this->db->selectFieldValues( $this->tables, $field, $this->conds, $this->caller, $this->options, $this->joinConds ); } /** * Run the constructed SELECT query, and return the first result row. If * there were no results, return false. * * @return \stdClass|false * @return-taint tainted */ public function fetchRow() { return $this->db->selectRow( $this->tables, $this->fields, $this->conds, $this->caller, $this->options, $this->joinConds ); } /** * Run the SELECT query, and return the number of results. This typically * uses a subquery to discard the actual results on the server side, and * is useful when counting rows with a limit. * * To count rows without a limit, it's more efficient to use a normal * COUNT() expression, for example: * * $queryBuilder->select( 'COUNT(*)' )->from( 'page' )->fetchField() * * @return int */ public function fetchRowCount(): int { return $this->db->selectRowCount( $this->tables, $this->getRowCountVar(), $this->conds, $this->caller, $this->options, $this->joinConds ); } /** * Estimate the number of rows in dataset * * MySQL allows you to estimate the number of rows that would be returned * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using * index cardinality statistics, and is notoriously inaccurate, especially * when large numbers of rows have recently been added or deleted. * * @return int */ public function estimateRowCount(): int { return $this->db->estimateRowCount( $this->tables, $this->getRowCountVar(), $this->conds, $this->caller, $this->options, $this->joinConds ); } /** * Private helper which extracts a field suitable for row counting from the * fields array * * @return string */ private function getRowCountVar() { if ( count( $this->fields ) === 0 ) { return '*'; } elseif ( count( $this->fields ) === 1 ) { return reset( $this->fields ); } else { throw new \UnexpectedValueException( __METHOD__ . ' expects the query to have at most one field' ); } } /** * Build a GROUP_CONCAT or equivalent statement for a query. * * This is useful for combining a field for several rows into a single string. * NULL values will not appear in the output, duplicated values will appear, * and the resulting delimiter-separated values have no defined sort order. * Code using the results may need to use the PHP unique() or sort() methods. * * @param string $delim * @return string */ public function buildGroupConcatField( $delim ) { if ( count( $this->fields ) !== 1 ) { throw new \UnexpectedValueException( __METHOD__ . ' expects the query to have only one field' ); } $field = reset( $this->fields ); return $this->db->buildGroupConcatField( $delim, $this->tables, $field, $this->conds, $this->joinConds ); } /** * Get the SQL query string which would be used by fetchResultSet(). * * @return string */ public function getSQL() { // Assume that whoever is calling this method is doing it to build a subquery $caller = $this->isCallerOverridden ? $this->caller : ISQLPlatform::CALLER_SUBQUERY; return $this->db->selectSQLText( $this->tables, $this->fields, $this->conds, $caller, $this->options, $this->joinConds ); } /** * Get an associative array describing the query in terms of its raw parameters to * IReadableDatabase::select(). This can be used to interface with legacy code. * * @param string $joinsName The name of the join_conds key * @return array The query info array, with keys: * - tables: The table array * - fields: The fields * - conds: The conditions * - options: The query options * - join_conds: The join conditions. This can also be given a different * name by passing a $joinsName parameter, since some legacy code uses * the name "joins". * - caller: The caller signature */ public function getQueryInfo( $joinsName = 'join_conds' ) { $info = [ 'tables' => $this->tables, 'fields' => $this->fields, 'conds' => $this->conds, 'options' => $this->options, ]; if ( $this->caller !== __CLASS__ ) { $info['caller'] = $this->caller; } $info[ $joinsName ] = $this->joinConds; return $info; } /** * Execute the query, but throw away the results. This is intended for * locking select queries. By calling this method, the caller is indicating * that the query is only done to acquire locks on the selected rows. The * field list is optional. * * Either forUpdate() or lockInShareMode() must be called before calling * this method. * * @see self::forUpdate() * @see self::lockInShareMode() * * @since 1.40 */ public function acquireRowLocks(): void { if ( !array_intersect( $this->options, [ 'FOR UPDATE', 'LOCK IN SHARE MODE' ] ) ) { throw new \UnexpectedValueException( __METHOD__ . ' can only be called ' . 'after forUpdate() or lockInShareMode()' ); } $fields = $this->fields ?: '1'; $this->db->select( $this->tables, $fields, $this->conds, $this->caller, $this->options, $this->joinConds ); } }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка