Completed
Push — master ( a9b98c...0233e0 )
by Ondřej
03:55
created

QueryResult::getIterator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Ivory\Result;
3
4
use Ivory\Exception\NotImplementedException;
5
use Ivory\Exception\ResultException;
6
use Ivory\Relation\Column;
7
use Ivory\Relation\FilteredRelation;
8
use Ivory\Relation\IColumn;
9
use Ivory\Relation\IRelation;
10
use Ivory\Relation\ITuple;
11
use Ivory\Relation\ProjectedRelation;
12
use Ivory\Relation\RelationSeekableIterator;
13
use Ivory\Relation\RelationMacros;
14
use Ivory\Relation\RenamedRelation;
15
use Ivory\Relation\Tuple;
16
use Ivory\Type\ITypeDictionary;
17
18
class QueryResult extends Result implements IQueryResult
19
{
20
    use RelationMacros;
21
22
    private $typeDictionary;
23
    private $numRows;
24
    private $populated = false;
25
    /** @var Column[] */
26
    private $columns;
27
    /** @var int[] map: column name => offset of the first column of the name */
28
    private $colNameMap;
29
30
31
    /**
32
     * @param resource $resultHandler the result, with the internal pointer at the beginning
33
     * @param ITypeDictionary $typeDictionary
34
     * @param string|null $lastNotice last notice captured on the connection
35
     */
36
    public function __construct($resultHandler, ITypeDictionary $typeDictionary, string $lastNotice = null)
37
    {
38
        parent::__construct($resultHandler, $lastNotice);
39
40
        $this->typeDictionary = $typeDictionary;
41
42
        $this->numRows = $this->fetchNumRows();
43
        $this->populate(); // not lazy - chances are, when the query was made, the caller will care about its results
44
    }
45
46
    private function fetchNumRows(): int
47
    {
48
        $numRows = pg_num_rows($this->handler);
49
        if ($numRows >= 0 && $numRows !== null) { // NOTE: besides -1, pg_num_rows() might return NULL on error
50
            return $numRows;
51
        } else {
52
            throw new ResultException('Error retrieving number of rows of the result.');
53
        }
54
    }
55
56
57
    //region ICachingDataProcessor
58
59
    public function populate()
60
    {
61
        if ($this->populated) {
62
            return;
63
        }
64
65
        $numFields = pg_num_fields($this->handler);
66
        if ($numFields < 0 || $numFields === null) {
67
            throw new ResultException('Error retrieving number of fields of the result.');
68
        }
69
        $this->columns = [];
70
        $this->colNameMap = [];
71
        for ($i = 0; $i < $numFields; $i++) {
72
            /* NOTE: pg_field_type() cannot be used for simplicity - multiple types of the same name might exist in
73
             *       different schemas. Thus, the only reasonable way to recognize the types is using their OIDs,
74
             *       returned by pg_field_type_oid(). Up to some extreme cases, within a given database, the same OID
75
             *       will always refer to the same data type.
76
             */
77
            $name = pg_field_name($this->handler, $i);
78
            if ($name === false || $name === null) { // NOTE: besides false, pg_field_name() might return NULL on error
79
                throw new ResultException("Error retrieving name of result column $i.");
80
            }
81
            if ($name == '?column?') {
82
                $name = null;
83
            }
84
            $typeOid = pg_field_type_oid($this->handler, $i);
85
            if ($typeOid === false || $typeOid === null) { // NOTE: besides false, pg_field_type_oid() might return NULL on error
86
                throw new ResultException("Error retrieving type OID of result column $i.");
87
            }
88
            $type = $this->typeDictionary->requireTypeByOid($typeOid);
89
90
            $this->columns[] = new Column($this, $i, $name, $type);
91
92
            if ($name !== null && !isset($this->colNameMap[$name])) {
93
                $this->colNameMap[$name] = $i;
94
            }
95
        }
96
97
        $this->populated = true;
98
    }
99
100
    public function flush()
101
    {
102
        $this->populated = false;
103
        $this->populate(); // re-initialize the internal data right away for the other methods not to call populate() over and over again
104
    }
105
106
    //endregion
107
108
    //region IRelation
109
110
    public function getColumns()
111
    {
112
        return $this->columns;
113
    }
114
115
    public function filter($decider): IRelation
116
    {
117
        return new FilteredRelation($this, $decider);
118
    }
119
120
    public function project($columns): IRelation
121
    {
122
        return new ProjectedRelation($this, $columns);
123
    }
124
125
    public function rename($renamePairs): IRelation
126
    {
127
        return new RenamedRelation($this, $renamePairs);
128
    }
129
130
    public function uniq($hasher = null, $comparator = null): IRelation
131
    {
132
        throw new NotImplementedException();
133
    }
134
135
    public function tuple(int $offset = 0): ITuple
136
    {
137 View Code Duplication
        if ($offset >= $this->numRows || $offset < -$this->numRows) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
            throw new \OutOfBoundsException("Offset $offset is out of the result bounds [0,{$this->numRows})");
139
        }
140
141
        $effectiveOffset = ($offset >= 0 ? $offset : $this->numRows + $offset);
142
143
        $rawData = pg_fetch_row($this->handler, $effectiveOffset);
144
        if ($rawData === false || $rawData === null) {
145
            throw new ResultException("Error fetching row at offset $offset");
146
        }
147
148
        $data = [];
149
        foreach ($this->columns as $i => $col) {
150
            $data[$i] = $col->getType()->parseValue($rawData[$i]);
151
        }
152
153
        return new Tuple($data, $this->columns, $this->colNameMap);
0 ignored issues
show
Documentation introduced by
$this->colNameMap is of type array<integer,integer>, but the function expects a array<integer,object<int>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
154
    }
155
156
    //endregion
157
158
    //region \Countable
159
160
    public function count()
161
    {
162
        return $this->numRows;
163
    }
164
165
    //endregion
166
}
167