MediaWiki  master
BatchRowIterator.php
Go to the documentation of this file.
1 <?php
2 
4 
33 class BatchRowIterator implements RecursiveIterator {
34 
38  protected $db;
39 
43  protected $table;
44 
48  protected $primaryKey;
49 
53  protected $batchSize;
54 
58  protected $conditions = [];
59 
63  protected $joinConditions = [];
64 
69  protected $fetchColumns;
70 
74  protected $orderBy;
75 
79  private $current = [];
80 
84  private $key;
85 
89  protected $options = [];
90 
94  protected $caller;
95 
106  if ( $batchSize < 1 ) {
107  throw new InvalidArgumentException( 'Batch size must be at least 1 row.' );
108  }
109  $this->db = $db;
110  $this->table = $table;
111  $this->primaryKey = (array)$primaryKey;
112  $this->fetchColumns = $this->primaryKey;
113  $this->orderBy = implode( ' ASC,', $this->primaryKey ) . ' ASC';
114  $this->batchSize = $batchSize;
115  }
116 
121  public function addConditions( array $conditions ) {
122  $this->conditions = array_merge( $this->conditions, $conditions );
123  }
124 
129  public function addOptions( array $options ) {
130  $this->options = array_merge( $this->options, $options );
131  }
132 
137  public function addJoinConditions( array $conditions ) {
138  $this->joinConditions = array_merge( $this->joinConditions, $conditions );
139  }
140 
145  public function setFetchColumns( array $columns ) {
146  // If it's not the all column selector merge in the primary keys we need
147  if ( count( $columns ) === 1 && reset( $columns ) === '*' ) {
148  $this->fetchColumns = $columns;
149  } else {
150  $this->fetchColumns = array_unique( array_merge(
151  $this->primaryKey,
152  $columns
153  ) );
154  }
155  }
156 
165  public function setCaller( $caller ) {
166  $this->caller = $caller;
167 
168  return $this;
169  }
170 
177  public function extractPrimaryKeys( $row ) {
178  $pk = [];
179  foreach ( $this->primaryKey as $alias => $column ) {
180  $name = is_numeric( $alias ) ? $column : $alias;
181  $pk[$name] = $row->{$name};
182  }
183  return $pk;
184  }
185 
189  public function current() {
190  return $this->current;
191  }
192 
196  public function key() {
197  return $this->key;
198  }
199 
203  public function rewind() {
204  $this->key = -1; // self::next() will turn this into 0
205  $this->current = [];
206  $this->next();
207  }
208 
212  public function valid() {
213  return (bool)$this->current;
214  }
215 
219  public function hasChildren() {
220  return $this->current && count( $this->current );
221  }
222 
226  public function getChildren() {
227  return new NotRecursiveIterator( new ArrayIterator( $this->current ) );
228  }
229 
233  public function next() {
234  $caller = __METHOD__;
235  if ( (string)$this->caller !== '' ) {
236  $caller .= " (for {$this->caller})";
237  }
238 
239  $res = $this->db->select(
240  $this->table,
241  $this->fetchColumns,
242  $this->buildConditions(),
243  $caller,
244  [
245  'LIMIT' => $this->batchSize,
246  'ORDER BY' => $this->orderBy,
247  ] + $this->options,
248  $this->joinConditions
249  );
250 
251  // The iterator is converted to an array because in addition to
252  // returning it in self::current() we need to use the end value
253  // in self::buildConditions()
254  $this->current = iterator_to_array( $res );
255  $this->key++;
256  }
257 
270  protected function buildConditions() {
271  if ( !$this->current ) {
272  return $this->conditions;
273  }
274 
275  $maxRow = end( $this->current );
276  $maximumValues = [];
277  foreach ( $this->primaryKey as $alias => $column ) {
278  $name = is_numeric( $alias ) ? $column : $alias;
279  $maximumValues[$column] = $this->db->addQuotes( $maxRow->{$name} );
280  }
281 
282  $pkConditions = [];
283  // For example: If we have 3 primary keys
284  // first run through will generate
285  // col1 = 4 AND col2 = 7 AND col3 > 1
286  // second run through will generate
287  // col1 = 4 AND col2 > 7
288  // and the final run through will generate
289  // col1 > 4
290  while ( $maximumValues ) {
291  $pkConditions[] = $this->buildGreaterThanCondition( $maximumValues );
292  array_pop( $maximumValues );
293  }
294 
295  $conditions = $this->conditions;
296  $conditions[] = sprintf( '( %s )', implode( ' ) OR ( ', $pkConditions ) );
297 
298  return $conditions;
299  }
300 
313  protected function buildGreaterThanCondition( array $quotedMaximumValues ) {
314  $keys = array_keys( $quotedMaximumValues );
315  $lastColumn = end( $keys );
316  $lastValue = array_pop( $quotedMaximumValues );
317  $conditions = [];
318  foreach ( $quotedMaximumValues as $column => $value ) {
319  $conditions[] = "$column = $value";
320  }
321  $conditions[] = "$lastColumn > $lastValue";
322 
323  return implode( ' AND ', $conditions );
324  }
325 }
Allows iterating a large number of rows in batches transparently.
next()
Fetch the next set of rows from the database.
addConditions(array $conditions)
array $primaryKey
The name of the primary key(s)
rewind()
Reset the iterator to the beginning of the table.
string array $table
The name or names of the table to read from.
array $options
Additional query options.
string null $caller
For debugging which method is using this class.
IDatabase $db
The database to read from.
addJoinConditions(array $conditions)
array $current
The current iterator value.
setFetchColumns(array $columns)
string $orderBy
SQL Order by condition generated from $this->primaryKey.
setCaller( $caller)
Use ->setCaller( METHOD ) to indicate which code is using this class.
extractPrimaryKeys( $row)
Extracts the primary key(s) from a database row.
addOptions(array $options)
array $conditions
Array of strings containing SQL conditions to add to the query.
array $fetchColumns
List of column names to select from the table suitable for use with IDatabase::select()
int $batchSize
The number of rows to fetch per iteration.
int $key
0-indexed number of pages fetched since self::reset()
buildGreaterThanCondition(array $quotedMaximumValues)
Given an array of column names and their maximum value generate an SQL condition where all keys excep...
buildConditions()
Uses the primary key list and the maximal result row from the previous iteration to build an SQL cond...
__construct(IDatabase $db, $table, $primaryKey, $batchSize)
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:39