Passed
Push — master ( 374fd3...a58c7c )
by Konrad
25:54
created

InsertQueryHandler::getNextMaxTermId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
/**
4
 * This file is part of the sweetrdf/InMemoryStoreSqlite package and licensed under
5
 * the terms of the GPL-2 license.
6
 *
7
 * (c) Konrad Abicht <[email protected]>
8
 * (c) Benjamin Nowak
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace sweetrdf\InMemoryStoreSqlite\Store\QueryHandler;
15
16
use sweetrdf\InMemoryStoreSqlite\Log\Logger;
17
use sweetrdf\InMemoryStoreSqlite\Store\InMemoryStoreSqlite;
18
19
use function sweetrdf\InMemoryStoreSqlite\getNormalizedValue;
20
21
class InsertQueryHandler extends QueryHandler
22
{
23
    /**
24
     * Is used if $bulkLoadModeIsActive is true. Determines next term ID for
25
     * entries in id2val, s2val and o2val.
26
     */
27
    private int $bulkLoadModeNextTermId = 1;
28
29
    /**
30
     * Is being used for blank nodes to generate a hash which is not only dependent on
31
     * blank node ID and graph, but also on a random value.
32
     * Otherwise blank nodes inserted in different "insert-sessions" will have the same reference.
33
     */
34
    private ?string $sessionId = null;
35
36 100
    public function __construct(InMemoryStoreSqlite $store, Logger $logger)
37
    {
38 100
        parent::__construct($store, $logger);
39
    }
40
41 94
    public function runQuery(array $infos)
42
    {
43 94
        $this->sessionId = bin2hex(random_bytes(4));
44 94
        $this->store->getDBObject()->getPDO()->beginTransaction();
45
46 94
        foreach ($infos['query']['construct_triples'] as $triple) {
47 94
            $this->addTripleToGraph($triple, $infos['query']['target_graph']);
48
        }
49
50 94
        $this->store->getDBObject()->getPDO()->commit();
51
52 94
        $this->sessionId = null;
53
    }
54
55 94
    private function addTripleToGraph(array $triple, string $graph): void
56
    {
57
        /*
58
         * information:
59
         *
60
         *  + val_hash: hashed version of given value
61
         *  + val_type: type of the term; one of: bnode, uri, literal
62
         */
63
64 94
        $triple = $this->prepareTriple($triple, $graph);
65
66
        /*
67
         * graph
68
         */
69 94
        $graphId = $this->getIdOfExistingTerm($graph, 'id');
70 94
        if (null == $graphId) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $graphId of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
71 94
            $graphId = $this->store->getDBObject()->insert('id2val', [
72 94
                'id' => $this->getNextMaxTermId(),
73 94
                'val' => $graph,
74 94
                'val_type' => 0, // = uri
75 94
            ]);
76
        }
77
78
        /*
79
         * s2val
80
         */
81 94
        $subjectId = $this->getIdOfExistingTerm($triple['s'], 'subject');
82 94
        if (null == $subjectId) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $subjectId of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
83 94
            $subjectId = $this->getNextMaxTermId();
84 94
            $this->store->getDBObject()->insert('s2val', [
85 94
                'id' => $subjectId,
86 94
                'val' => $triple['s'],
87 94
                'val_hash' => $this->getValueHash($triple['s']),
88 94
            ]);
89
        }
90
91
        /*
92
         * predicate
93
         */
94 94
        $predicateId = $this->getIdOfExistingTerm($triple['p'], 'id');
95 94
        if (null == $predicateId) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $predicateId of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
96 94
            $predicateId = $this->getNextMaxTermId();
97 94
            $this->store->getDBObject()->insert('id2val', [
98 94
                'id' => $predicateId,
99 94
                'val' => $triple['p'],
100 94
                'val_type' => 0, // = uri
101 94
            ]);
102
        }
103
104
        /*
105
         * o2val
106
         */
107 94
        $objectId = $this->getIdOfExistingTerm($triple['o'], 'object');
108 94
        if (null == $objectId) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $objectId of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
109 94
            $objectId = $this->getNextMaxTermId();
110 94
            $this->store->getDBObject()->insert('o2val', [
111 94
                'id' => $objectId,
112 94
                'val' => $triple['o'],
113 94
                'val_hash' => $this->getValueHash($triple['o']),
114 94
            ]);
115
        }
116
117
        /*
118
         * o_lang_dt
119
         *
120
         * Note: only one of these two should be set, but it may happen that it looks like:
121
         *
122
         *      o_lang => de
123
         *      o_datatype => http://www.w3.org/1999/02/22-rdf-syntax-ns#langString
124
         *
125
         * If o_lang is set, we always ignore o_datatype.
126
         */
127 94
        if (isset($triple['o_lang']) && !empty($triple['o_lang'])) {
128 10
            $oLangDt = $triple['o_lang'];
129
        } else {
130 93
            $oLangDt = $triple['o_datatype'];
131
        }
132
133 94
        $oLangDtId = $this->getIdOfExistingTerm($oLangDt, 'id');
134 94
        if (null == $oLangDtId) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $oLangDtId of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
135 94
            $oLangDtId = $this->getNextMaxTermId();
136 94
            $this->store->getDBObject()->insert('id2val', [
137 94
                'id' => $oLangDtId,
138 94
                'val' => $oLangDt,
139 94
                'val_type' => !empty($triple['o_datatype']) ? 0 : 2,
140 94
            ]);
141
        }
142
143
        /*
144
         * triple
145
         */
146 94
        $sql = 'SELECT * FROM triple WHERE s = ? AND p = ? AND o = ?';
147 94
        $check = $this->store->getDBObject()->fetchRow($sql, [$subjectId, $predicateId, $objectId]);
148 94
        if (false === $check) {
149 94
            $tripleId = $this->store->getDBObject()->insert('triple', [
150 94
                's' => $subjectId,
151 94
                's_type' => $triple['s_type_int'],
152 94
                'p' => $predicateId,
153 94
                'o' => $objectId,
154 94
                'o_type' => $triple['o_type_int'],
155 94
                'o_lang_dt' => $oLangDtId,
156 94
                'o_comp' => getNormalizedValue($triple['o']),
157 94
            ]);
158
        } else {
159 2
            $tripleId = $check['t'];
160
        }
161
162
        /*
163
         * triple to graph
164
         */
165 94
        $sql = 'SELECT * FROM g2t WHERE g = ? AND t = ?';
166 94
        $check = $this->store->getDBObject()->fetchRow($sql, [$graphId, $tripleId]);
167 94
        if (false == $check) {
168 94
            $this->store->getDBObject()->insert('g2t', [
169 94
                'g' => $graphId,
170 94
                't' => $tripleId,
171 94
            ]);
172
        }
173
    }
174
175 94
    private function prepareTriple(array $triple, string $graph): array
176
    {
177
        /*
178
         * subject: set type int
179
         */
180 94
        $triple['s_type_int'] = 0; // uri
181 94
        if ('bnode' == $triple['s_type']) {
182 9
            $triple['s_type_int'] = 1;
183 92
        } elseif ('literal' == $triple['s_type']) {
184
            $triple['s_type_int'] = 2;
185
        }
186
187
        /*
188
         * subject is a blank node
189
         */
190 94
        if ('bnode' == $triple['s_type']) {
191
            // transforms _:foo to _:b671320391_foo
192 9
            $s = $triple['s'];
193
            // TODO make bnode ID only unique for this session, not in general
194 9
            $triple['s'] = '_:b'.$this->getValueHash($this->sessionId.$graph.$s).'_';
195 9
            $triple['s'] .= substr($s, 2);
196
        }
197
198
        /*
199
         * object: set type int
200
         */
201 94
        $triple['o_type_int'] = 0; // uri
202 94
        if ('bnode' == $triple['o_type']) {
203 6
            $triple['o_type_int'] = 1;
204 93
        } elseif ('literal' == $triple['o_type']) {
205 66
            $triple['o_type_int'] = 2;
206
        }
207
208
        /*
209
         * object is a blank node
210
         */
211 94
        if ('bnode' == $triple['o_type']) {
212
            // transforms _:foo to _:b671320391_foo
213 6
            $o = $triple['o'];
214 6
            $triple['o'] = '_:b'.$this->getValueHash($this->sessionId.$graph.$o).'_';
215 6
            $triple['o'] .= substr($o, 2);
216
        }
217
218 94
        return $triple;
219
    }
220
221
    /**
222
     * Generates the next valid ID.
223
     *
224
     * @return int returns 1 or higher
225
     */
226 94
    private function getNextMaxTermId(): int
227
    {
228 94
        return $this->bulkLoadModeNextTermId++;
229
    }
230
231
    /**
232
     * @param string $type     One of: bnode, uri, literal
233
     * @param string $quadPart One of: id, subject, object
234
     *
235
     * @return int 1 (or higher), if available, or null
236
     */
237 94
    private function getIdOfExistingTerm(string $value, string $quadPart): ?int
238
    {
239
        // id (predicate or graph)
240 94
        if ('id' == $quadPart) {
241 94
            $sql = 'SELECT id, val FROM id2val WHERE val = ?';
242
243 94
            $entry = $this->store->getDBObject()->fetchRow($sql, [$value]);
244
245
            // entry found, use its ID
246 94
            if (\is_array($entry)) {
247 62
                return $entry['id'];
248
            } else {
249 94
                return null;
250
            }
251
        } else {
252
            // subject or object
253 94
            $table = 'subject' == $quadPart ? 's2val' : 'o2val';
254 94
            $sql = 'SELECT id, val FROM '.$table.' WHERE val_hash = ?';
255 94
            $params = [$this->getValueHash($value)];
256
257 94
            $entry = $this->store->getDBObject()->fetchRow($sql, $params);
258
259
            // entry found, use its ID
260 94
            if (isset($entry['val']) && $entry['val'] == $value) {
261 41
                return $entry['id'];
262
            } else {
263 94
                return null;
264
            }
265
        }
266
    }
267
}
268