return '';
}
- public function select( $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = [] ) {
+ public function select(
+ $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+ ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
return $this->query( $sql, $fname );
$options = [], $join_conds = []
) {
if ( is_array( $vars ) ) {
- $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
+ $fields = implode( ',', $this->fieldNamesWithAlias( $vars ) );
+ } else {
+ $fields = $vars;
}
$options = (array)$options;
? $options['IGNORE INDEX']
: [];
+ if (
+ $this->selectOptionsIncludeLocking( $options ) &&
+ $this->selectFieldsOrOptionsAggregate( $vars, $options )
+ ) {
+ // Some DB types (postgres/oracle) disallow FOR UPDATE with aggregate
+ // functions. Discourage use of such queries to encourage compatibility.
+ call_user_func(
+ $this->deprecationLogger,
+ __METHOD__ . ": aggregation used with a locking SELECT ($fname)."
+ );
+ }
+
if ( is_array( $table ) ) {
$from = ' FROM ' .
$this->tableNamesWithIndexClauseOrJOIN(
}
if ( $conds === '' ) {
- $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
+ $sql = "SELECT $startOpts $fields $from $useIndex $ignoreIndex $preLimitTail";
} elseif ( is_string( $conds ) ) {
- $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
+ $sql = "SELECT $startOpts $fields $from $useIndex $ignoreIndex " .
"WHERE $conds $preLimitTail";
} else {
throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
}
+ /**
+ * @param string|array $options
+ * @return bool
+ */
+ private function selectOptionsIncludeLocking( $options ) {
+ $options = (array)$options;
+ foreach ( [ 'FOR UPDATE', 'LOCK IN SHARE MODE' ] as $lock ) {
+ if ( in_array( $lock, $options, true ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param array|string $fields
+ * @param array|string $options
+ * @return bool
+ */
+ private function selectFieldsOrOptionsAggregate( $fields, $options ) {
+ foreach ( (array)$options as $key => $value ) {
+ if ( is_string( $key ) ) {
+ if ( preg_match( '/^(?:GROUP BY|HAVING)$/i', $key ) ) {
+ return true;
+ }
+ } elseif ( is_string( $value ) ) {
+ if ( preg_match( '/^(?:DISTINCT|DISTINCTROW)$/i', $value ) ) {
+ return true;
+ }
+ }
+ }
+
+ $regex = '/^(?:COUNT|MIN|MAX|SUM|GROUP_CONCAT|LISTAGG|ARRAY_AGG)\s*\\(/i';
+ foreach ( (array)$fields as $field ) {
+ if ( is_string( $field ) && preg_match( $regex, $field ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* @param array|string $conds
* @param string $fname
* @covers Wikimedia\Rdbms\Database::makeSelectOptions
* @covers Wikimedia\Rdbms\Database::makeOrderBy
* @covers Wikimedia\Rdbms\Database::makeGroupByWithHaving
+ * @covers Wikimedia\Rdbms\Database::selectFieldsOrOptionsAggregate
+ * @covers Wikimedia\Rdbms\Database::selectOptionsIncludeLocking
*/
public function testSelect( $sql, $sqlText ) {
$this->database->select(
[
'tables' => 'table',
'fields' => [ 'field' ],
- 'options' => [ 'DISTINCT', 'LOCK IN SHARE MODE' ],
+ 'options' => [ 'DISTINCT' ],
],
- "SELECT DISTINCT field FROM table LOCK IN SHARE MODE"
+ "SELECT DISTINCT field FROM table"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field' ],
+ 'options' => [ 'LOCK IN SHARE MODE' ],
+ ],
+ "SELECT field FROM table LOCK IN SHARE MODE"
],
[
[