sanchezzzhak / kak-clickhouse

Yii2 ext. ClickHouse
69 stars 42 forks source link

BatchQueryResult compatibility with Iterable interface #68

Open marievych opened 3 months ago

marievych commented 3 months ago

https://github.com/sanchezzzhak/kak-clickhouse/blob/29a317fc60da43b38408f9ecddfd6f7f9faa9810/BatchQueryResult.php#L76

BatchQueryResult should be modified to

<?php

namespace kak\clickhouse;

use yii\base\BaseObject;

/**
 * BatchQueryResult represents a batch query from which you can retrieve data in batches.
 *
 * You usually do not instantiate BatchQueryResult directly. Instead, you obtain it by
 * calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the [[\Iterator]] interface,
 * you can iterate it to obtain a batch of data in each iteration. For example,
 *
 * ```php
 * $query = (new Query)->from('user');
 * foreach ($query->batch() as $i => $users) {
 *     // $users represents the rows in the $i-th batch
 * }
 * foreach ($query->each() as $user) {
 * }
 * ```
 *
 * Class BatchQueryResult
 * @package kak\clickhouse
 */
class BatchQueryResult  extends BaseObject implements \Iterator
{
    /**
     * @var Connection the DB connection to be used when performing batch query.
     * If null, the "db" application component will be used.
     */
    public $db;
    /**
     * @var Query the query object associated with this batch query.
     * Do not modify this property directly unless after [[reset()]] is called explicitly.
     */
    public $query;
    /**
     * @var int the number of rows to be returned in each batch.
     */
    public $batchSize = 100;

    /**
     * @var bool whether to return a single row during each iteration.
     * If false, a whole batch of rows will be returned in each iteration.
     */
    public $each = false;

    /**
     * @var array the data retrieved in the current batch
     */
    private $batch;
    /**
     * @var mixed the value for the current iteration
     */
    private $value;
    /**
     * @var string|int the key for the current iteration
     */
    private $key;

    private $index = 0;

    /**
     * Destructor.
     */
    public function __destruct()
    {
        $this->reset();
    }

    /**
     * Resets the batch query.
     * This method will clean up the existing batch query so that a new batch query can be performed.
     */
    public function reset(): void
    {
        $this->batch = null;
        $this->value = null;
        $this->key = null;
        $this->index = 0;
    }

    /**
     * Resets the iterator to the initial state.
     * This method is required by the interface [[\Iterator]].
     */
    public function rewind(): void
    {
        $this->reset();
        $this->next();
    }

    /**
     * Moves the internal pointer to the next dataset.
     * This method is required by the interface [[\Iterator]].
     */
    public function next(): void
    {
        if ($this->batch === null || !$this->each || $this->each && next($this->batch) === false) {
            $this->batch = $this->fetchData();
            reset($this->batch);
        }

        if ($this->each) {
            $this->value = current($this->batch);
            if ($this->query->indexBy !== null) {
                $this->key = key($this->batch);
            } elseif (key($this->batch) !== null) {
                $this->key = $this->key === null ? 0 : $this->key + 1;
            } else {
                $this->key = null;
            }
        } else {
            $this->value = $this->batch;
            $this->key = $this->key === null ? 0 : $this->key + 1;
        }
    }

    /**
     * Fetches the next batch of data.
     * @return array the data fetched
     */
    protected function fetchData(): array
    {
        $command = $this->query->createCommand($this->db);

        $offset = ($this->index *  $this->batchSize);
        $this->index++;
        $limit = $this->batchSize;
        $rawSql = $command->getRawSql();
        $command->setSql("{$rawSql} LIMIT {$offset},{$limit}");

        $rows = $command->queryAll();
        return $this->query->populate($rows);
    }

    /**
     * Returns the index of the current dataset.
     * This method is required by the interface [[\Iterator]].
     * @return string|int the index of the current row.
     */
    public function key(): string|int
    {
        return $this->key;
    }

    /**
     * Returns the current dataset.
     * This method is required by the interface [[\Iterator]].
     * @return mixed the current dataset.
     */
    public function current(): mixed
    {
        return $this->value;
    }

    /**
     * Returns whether there is a valid dataset at the current position.
     * This method is required by the interface [[\Iterator]].
     * @return bool whether there is a valid dataset at the current position.
     */
    public function valid(): bool
    {
        return !empty($this->batch);
    }

}
sanchezzzhak commented 3 months ago

Hi. Thx for create issue. We must comply with interface version php 7.3, as this is the minimum version of the yii2 framework. At the moment we are i using php 8.3 and so far everything is working well.