1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Database;
9:
10: use Nette;
11: use PDO;
12:
13:
14: 15: 16:
17: class ResultSet implements \Iterator, IRowContainer
18: {
19: use Nette\SmartObject;
20:
21:
22: private $connection;
23:
24:
25: private $supplementalDriver;
26:
27:
28: private $pdoStatement;
29:
30:
31: private $result;
32:
33:
34: private $resultKey = -1;
35:
36:
37: private $results;
38:
39:
40: private $time;
41:
42:
43: private $queryString;
44:
45:
46: private $params;
47:
48:
49: private $types;
50:
51:
52: public function __construct(Connection $connection, $queryString, array $params)
53: {
54: $time = microtime(true);
55: $this->connection = $connection;
56: $this->supplementalDriver = $connection->getSupplementalDriver();
57: $this->queryString = $queryString;
58: $this->params = $params;
59:
60: try {
61: if (substr($queryString, 0, 2) === '::') {
62: $connection->getPdo()->{substr($queryString, 2)}();
63: } elseif ($queryString !== null) {
64: static $types = ['boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT,
65: 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL, ];
66: $this->pdoStatement = $connection->getPdo()->prepare($queryString);
67: foreach ($params as $key => $value) {
68: $type = gettype($value);
69: $this->pdoStatement->bindValue(is_int($key) ? $key + 1 : $key, $value, isset($types[$type]) ? $types[$type] : PDO::PARAM_STR);
70: }
71: $this->pdoStatement->setFetchMode(PDO::FETCH_ASSOC);
72: $this->pdoStatement->execute();
73: }
74: } catch (\PDOException $e) {
75: $e = $this->supplementalDriver->convertException($e);
76: $e->queryString = $queryString;
77: throw $e;
78: }
79: $this->time = microtime(true) - $time;
80: }
81:
82:
83: 84: 85:
86: public function getConnection()
87: {
88: return $this->connection;
89: }
90:
91:
92: 93: 94: 95:
96: public function getPdoStatement()
97: {
98: return $this->pdoStatement;
99: }
100:
101:
102: 103: 104:
105: public function getQueryString()
106: {
107: return $this->queryString;
108: }
109:
110:
111: 112: 113:
114: public function getParameters()
115: {
116: return $this->params;
117: }
118:
119:
120: 121: 122:
123: public function getColumnCount()
124: {
125: return $this->pdoStatement ? $this->pdoStatement->columnCount() : null;
126: }
127:
128:
129: 130: 131:
132: public function getRowCount()
133: {
134: return $this->pdoStatement ? $this->pdoStatement->rowCount() : null;
135: }
136:
137:
138: 139: 140:
141: public function getTime()
142: {
143: return $this->time;
144: }
145:
146:
147: 148: 149: 150: 151:
152: public function normalizeRow($row)
153: {
154: if ($this->types === null) {
155: $this->types = (array) $this->supplementalDriver->getColumnTypes($this->pdoStatement);
156: }
157:
158: foreach ($this->types as $key => $type) {
159: $value = $row[$key];
160: if ($value === null || $value === false || $type === IStructure::FIELD_TEXT) {
161:
162: } elseif ($type === IStructure::FIELD_INTEGER) {
163: $row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;
164:
165: } elseif ($type === IStructure::FIELD_FLOAT) {
166: if (($pos = strpos($value, '.')) !== false) {
167: $value = rtrim(rtrim($pos === 0 ? "0$value" : $value, '0'), '.');
168: }
169: $float = (float) $value;
170: $row[$key] = (string) $float === $value ? $float : $value;
171:
172: } elseif ($type === IStructure::FIELD_BOOL) {
173: $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
174:
175: } elseif ($type === IStructure::FIELD_DATETIME || $type === IStructure::FIELD_DATE || $type === IStructure::FIELD_TIME) {
176: $row[$key] = new Nette\Utils\DateTime($value);
177:
178: } elseif ($type === IStructure::FIELD_TIME_INTERVAL) {
179: preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)(\.\d+)?\z#', $value, $m);
180: $row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
181: if (PHP_VERSION_ID >= 70100) {
182: $row[$key]->f = isset($m[5]) ? (float) $m[5] : 0.0;
183: }
184: $row[$key]->invert = (int) (bool) $m[1];
185:
186: } elseif ($type === IStructure::FIELD_UNIX_TIMESTAMP) {
187: $row[$key] = Nette\Utils\DateTime::from($value);
188: }
189: }
190:
191: return $this->supplementalDriver->normalizeRow($row);
192: }
193:
194:
195:
196:
197:
198: 199: 200: 201:
202: public function dump()
203: {
204: Helpers::dumpResult($this);
205: }
206:
207:
208:
209:
210:
211: public function rewind()
212: {
213: if ($this->result === false) {
214: throw new Nette\InvalidStateException('Nette\\Database\\ResultSet implements only one way iterator.');
215: }
216: }
217:
218:
219: public function current()
220: {
221: return $this->result;
222: }
223:
224:
225: public function key()
226: {
227: return $this->resultKey;
228: }
229:
230:
231: public function next()
232: {
233: $this->result = false;
234: }
235:
236:
237: public function valid()
238: {
239: if ($this->result) {
240: return true;
241: }
242:
243: return $this->fetch() !== false;
244: }
245:
246:
247:
248:
249:
250: 251: 252:
253: public function fetch()
254: {
255: $data = $this->pdoStatement ? $this->pdoStatement->fetch() : null;
256: if (!$data) {
257: $this->pdoStatement->closeCursor();
258: return false;
259:
260: } elseif ($this->result === null && count($data) !== $this->pdoStatement->columnCount()) {
261: $duplicates = Helpers::findDuplicates($this->pdoStatement);
262: trigger_error("Found duplicate columns in database result set: $duplicates.", E_USER_NOTICE);
263: }
264:
265: $row = new Row;
266: foreach ($this->normalizeRow($data) as $key => $value) {
267: if ($key !== '') {
268: $row->$key = $value;
269: }
270: }
271:
272: $this->resultKey++;
273: return $this->result = $row;
274: }
275:
276:
277: 278: 279: 280: 281:
282: public function fetchField($column = 0)
283: {
284: $row = $this->fetch();
285: return $row ? $row[$column] : false;
286: }
287:
288:
289: 290: 291:
292: public function fetchPairs($key = null, $value = null)
293: {
294: return Helpers::toPairs($this->fetchAll(), $key, $value);
295: }
296:
297:
298: 299: 300:
301: public function fetchAll()
302: {
303: if ($this->results === null) {
304: $this->results = iterator_to_array($this);
305: }
306: return $this->results;
307: }
308:
309:
310: 311: 312:
313: public function fetchAssoc($path)
314: {
315: return Nette\Utils\Arrays::associate($this->fetchAll(), $path);
316: }
317: }
318: