Passed
Push — master ( e615f0...69cf9d )
by Alexander
39:55
created

BatchQueryResult::getDbDriverName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 11
ccs 0
cts 0
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use yii\base\BaseObject;
11
12
/**
13
 * BatchQueryResult represents a batch query from which you can retrieve data in batches.
14
 *
15
 * You usually do not instantiate BatchQueryResult directly. Instead, you obtain it by
16
 * calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the [[\Iterator]] interface,
17
 * you can iterate it to obtain a batch of data in each iteration. For example,
18
 *
19
 * ```php
20
 * $query = (new Query)->from('user');
21
 * foreach ($query->batch() as $i => $users) {
22
 *     // $users represents the rows in the $i-th batch
23
 * }
24
 * foreach ($query->each() as $user) {
25
 * }
26
 * ```
27
 *
28
 * @author Qiang Xue <[email protected]>
29
 * @since 2.0
30
 */
31
class BatchQueryResult extends BaseObject implements \Iterator
32
{
33
    /**
34
     * @var Connection the DB connection to be used when performing batch query.
35
     * If null, the "db" application component will be used.
36
     */
37
    public $db;
38
    /**
39
     * @var Query the query object associated with this batch query.
40
     * Do not modify this property directly unless after [[reset()]] is called explicitly.
41
     */
42
    public $query;
43
    /**
44
     * @var int the number of rows to be returned in each batch.
45
     */
46
    public $batchSize = 100;
47
    /**
48
     * @var bool whether to return a single row during each iteration.
49
     * If false, a whole batch of rows will be returned in each iteration.
50
     */
51
    public $each = false;
52
53
    /**
54
     * @var DataReader the data reader associated with this batch query.
55
     */
56
    private $_dataReader;
57
    /**
58
     * @var array the data retrieved in the current batch
59
     */
60
    private $_batch;
61
    /**
62
     * @var mixed the value for the current iteration
63
     */
64
    private $_value;
65
    /**
66
     * @var string|int the key for the current iteration
67
     */
68
    private $_key;
69
    /**
70
     * @var int MSSQL error code for exception that is thrown when last batch is size less than specified batch size
71
     * @see https://github.com/yiisoft/yii2/issues/10023
72
     */
73
    private $mssqlNoMoreRowsErrorCode = -13;
74
75
76
    /**
77
     * Destructor.
78
     */
79 8
    public function __destruct()
80
    {
81
        // make sure cursor is closed
82 8
        $this->reset();
83 8
    }
84
85
    /**
86
     * Resets the batch query.
87
     * This method will clean up the existing batch query so that a new batch query can be performed.
88
     */
89 8
    public function reset()
90
    {
91 8
        if ($this->_dataReader !== null) {
92 8
            $this->_dataReader->close();
93
        }
94 8
        $this->_dataReader = null;
95 8
        $this->_batch = null;
96 8
        $this->_value = null;
97 8
        $this->_key = null;
98 8
    }
99
100
    /**
101
     * Resets the iterator to the initial state.
102
     * This method is required by the interface [[\Iterator]].
103
     */
104 8
    public function rewind()
105
    {
106 8
        $this->reset();
107 8
        $this->next();
108 8
    }
109
110
    /**
111
     * Moves the internal pointer to the next dataset.
112
     * This method is required by the interface [[\Iterator]].
113
     */
114 8
    public function next()
115
    {
116 8
        if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) {
117 8
            $this->_batch = $this->fetchData();
118 8
            reset($this->_batch);
119
        }
120
121 8
        if ($this->each) {
122 4
            $this->_value = current($this->_batch);
123 4
            if ($this->query->indexBy !== null) {
124 4
                $this->_key = key($this->_batch);
125 4
            } elseif (key($this->_batch) !== null) {
126 4
                $this->_key = $this->_key === null ? 0 : $this->_key + 1;
127
            } else {
128 4
                $this->_key = null;
129
            }
130
        } else {
131 8
            $this->_value = $this->_batch;
132 8
            $this->_key = $this->_key === null ? 0 : $this->_key + 1;
133
        }
134 8
    }
135
136
    /**
137
     * Fetches the next batch of data.
138
     * @return array the data fetched
139
     * @throws Exception
140
     */
141 8
    protected function fetchData()
142
    {
143 8
        if ($this->_dataReader === null) {
144 8
            $this->_dataReader = $this->query->createCommand($this->db)->query();
145
        }
146
147 8
        $rows = $this->getRows();
148
149 8
        return $this->query->populate($rows);
150
    }
151
152
    /**
153
     * Reads and collects rows for batch
154
     * @return array
155
     * @since 2.0.23
156
     */
157 8
    protected function getRows()
158
    {
159 8
        $rows = [];
160 8
        $count = 0;
161
162
        try {
163 8
            while ($count++ < $this->batchSize && ($row = $this->_dataReader->read())) {
164 8
                $rows[] = $row;
165
            }
166 2
        } catch (\PDOException $e) {
167 2
            $errorCode = isset($e->errorInfo[1]) ? $e->errorInfo[1] : null;
168 2
            if ($this->getDbDriverName() !== 'sqlsrv' || $errorCode !== $this->mssqlNoMoreRowsErrorCode) {
169
                throw $e;
170
            }
171
        }
172
173 8
        return $rows;
174
    }
175
176
    /**
177
     * Returns the index of the current dataset.
178
     * This method is required by the interface [[\Iterator]].
179
     * @return int the index of the current row.
180
     */
181 4
    public function key()
182
    {
183 4
        return $this->_key;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_key also could return the type string which is incompatible with the documented return type integer.
Loading history...
184
    }
185
186
    /**
187
     * Returns the current dataset.
188
     * This method is required by the interface [[\Iterator]].
189
     * @return mixed the current dataset.
190
     */
191 8
    public function current()
192
    {
193 8
        return $this->_value;
194
    }
195
196
    /**
197
     * Returns whether there is a valid dataset at the current position.
198
     * This method is required by the interface [[\Iterator]].
199
     * @return bool whether there is a valid dataset at the current position.
200
     */
201 8
    public function valid()
202
    {
203 8
        return !empty($this->_batch);
204
    }
205
206
    /**
207
     * Gets db driver name from the db connection that is passed to the `batch()`, if it is not passed it uses
208
     * connection from the active record model
209
     * @return string|null
210
     */
211
    private function getDbDriverName()
212
    {
213
        if (isset($this->db->driverName)) {
214
            return $this->db->driverName;
215
        }
216
217
        if (isset($this->_batch[0]->db->driverName)) {
218
            return $this->_batch[0]->db->driverName;
219
        }
220
221
        return null;
222
    }
223
}
224