InMemoryStoreSqlite   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 224
Duplicated Lines 0 %

Test Coverage

Coverage 93.64%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 107
c 1
b 0
f 0
dl 0
loc 224
ccs 103
cts 110
cp 0.9364
rs 10
wmc 29

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getDBVersion() 0 3 1
A getNamespaceHelper() 0 3 1
A getDBObject() 0 3 1
A getLoggerPool() 0 3 1
A addRawTriples() 0 6 1
C query() 0 83 14
A createInstance() 0 8 1
A __construct() 0 14 1
B addQuads() 0 40 8
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 Nowack
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;
15
16
use Exception;
17
use rdfInterface\BlankNodeInterface;
18
use rdfInterface\DataFactoryInterface;
19
use rdfInterface\LiteralInterface;
20
use rdfInterface\NamedNodeInterface;
21
use rdfInterface\QuadIteratorInterface;
22
use simpleRdf\DataFactory;
23
use sweetrdf\InMemoryStoreSqlite\Log\LoggerPool;
24
use sweetrdf\InMemoryStoreSqlite\NamespaceHelper;
25
use sweetrdf\InMemoryStoreSqlite\Parser\SPARQLPlusParser;
26
use sweetrdf\InMemoryStoreSqlite\PDOSQLiteAdapter;
27
use sweetrdf\InMemoryStoreSqlite\Store\QueryHandler\AskQueryHandler;
28
use sweetrdf\InMemoryStoreSqlite\Store\QueryHandler\ConstructQueryHandler;
29
use sweetrdf\InMemoryStoreSqlite\Store\QueryHandler\DeleteQueryHandler;
30
use sweetrdf\InMemoryStoreSqlite\Store\QueryHandler\DescribeQueryHandler;
31
use sweetrdf\InMemoryStoreSqlite\Store\QueryHandler\InsertQueryHandler;
32
use sweetrdf\InMemoryStoreSqlite\Store\QueryHandler\SelectQueryHandler;
33
use sweetrdf\InMemoryStoreSqlite\StringReader;
34
35
class InMemoryStoreSqlite
36
{
37
    private PDOSQLiteAdapter $db;
38
39
    private DataFactoryInterface $dataFactory;
40
41
    private InsertQueryHandler $insertQueryHandler;
42
43
    private LoggerPool $loggerPool;
44
45
    private NamespaceHelper $namespaceHelper;
46
47
    private StringReader $stringReader;
48
49
    /**
50
     * @internal Don't use the constructor directly because parameters may change. Use createInstance() instead.
51
     */
52 100
    public function __construct(
53
        PDOSQLiteAdapter $db,
54
        DataFactoryInterface $dataFactory,
55
        NamespaceHelper $namespaceHelper,
56
        LoggerPool $loggerPool,
57
        StringReader $stringReader
58
    ) {
59 100
        $this->db = $db;
60 100
        $this->dataFactory = $dataFactory;
61 100
        $this->loggerPool = $loggerPool;
62 100
        $this->namespaceHelper = $namespaceHelper;
63 100
        $this->stringReader = $stringReader;
64
65 100
        $this->insertQueryHandler = new InsertQueryHandler($this, $this->loggerPool->createNewLogger('QueryHandler'));
66
    }
67
68
    /**
69
     * Shortcut for people who want a ready-to-use instance.
70
     */
71 100
    public static function createInstance()
72
    {
73 100
        return new self(
74 100
            new PDOSQLiteAdapter(),
75 100
            new DataFactory(),
76 100
            new NamespaceHelper(),
77 100
            new LoggerPool(),
78 100
            new StringReader()
79 100
        );
80
    }
81
82 3
    public function getLoggerPool(): LoggerPool
83
    {
84 3
        return $this->loggerPool;
85
    }
86
87
    public function getNamespaceHelper(): NamespaceHelper
88
    {
89
        return $this->namespaceHelper;
90
    }
91
92 96
    public function getDBObject(): ?PDOSQLiteAdapter
93
    {
94 96
        return $this->db;
95
    }
96
97 1
    public function getDBVersion()
98
    {
99 1
        return $this->db->getServerVersion();
100
    }
101
102 2
    public function addQuads(iterable | QuadIteratorInterface $quads): void
103
    {
104 2
        $triples = [];
105
106 2
        foreach ($quads as $quad) {
107 2
            $graphIri = NamespaceHelper::BASE_NAMESPACE;
108 2
            if (null !== $quad->getGraph()) {
109 2
                $graphIri = $quad->getGraph()->getValue();
110
            }
111
112 2
            if (!isset($triples[$graphIri])) {
113 2
                $triples[$graphIri] = [];
114
            }
115
116 2
            $triple = [
117 2
                's' => $quad->getSubject()->getValue(),
118 2
                's_type' => $quad->getSubject() instanceof NamedNodeInterface ? 'uri' : 'bnode',
119 2
                'p' => $quad->getPredicate()->getValue(),
120 2
                'o' => $quad->getObject()->getValue(),
121 2
                'o_type' => '',
122 2
                'o_lang' => '',
123 2
                'o_datatype' => '',
124 2
            ];
125
126
            // o
127 2
            if ($quad->getObject() instanceof NamedNodeInterface) {
128 2
                $triple['o_type'] = 'uri';
129 2
            } elseif ($quad->getObject() instanceof BlankNodeInterface) {
130
                $triple['o_type'] = 'bnode';
131
            } else {
132 2
                $triple['o_type'] = 'literal';
133 2
                $triple['o_lang'] = $quad->getObject()->getLang();
134 2
                $triple['o_datatype'] = $quad->getObject()->getDatatype();
135
            }
136
137 2
            $triples[$graphIri][] = $triple;
138
        }
139
140 2
        foreach ($triples as $graphIri => $entries) {
141 2
            $this->addRawTriples($entries, $graphIri);
142
        }
143
    }
144
145
    /**
146
     * Adds an array of raw triple-arrays to the store.
147
     *
148
     * Each triple-array in $triples has to look similar to:
149
     *
150
     *      [
151
     *          's' => 'http...',
152
     *          'p' => '...',
153
     *          'o' => '...',
154
     *          's_type' => 'uri',
155
     *          'o_type' => '...',
156
     *          'o_lang' => '...',
157
     *          'o_datatype' => '...',
158
     *      ]
159
     */
160 2
    public function addRawTriples(array $triples, string $graphIri): void
161
    {
162 2
        $this->insertQueryHandler->runQuery([
163 2
            'query' => [
164 2
                'construct_triples' => $triples,
165 2
                'target_graph' => $graphIri,
166 2
            ],
167 2
        ]);
168
    }
169
170
    /**
171
     * Executes a SPARQL query.
172
     *
173
     * @param string $q      SPARQL query
174
     * @param string $format One of: raw, instances
175
     */
176 98
    public function query(string $q, string $format = 'raw'): array | LiteralInterface
177
    {
178 98
        $errors = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $errors is dead and can be removed.
Loading history...
179
180 98
        if (preg_match('/^dump/i', $q)) {
181
            $infos = ['query' => ['type' => 'dump']];
182
        } else {
183 98
            $parserLogger = $this->loggerPool->createNewLogger('SPARQL');
184 98
            $p = new SPARQLPlusParser($parserLogger, $this->namespaceHelper, $this->stringReader);
185 98
            $p->parse($q);
186 98
            $infos = $p->getQueryInfos();
187 98
            $errors = $parserLogger->getEntries('error');
188
189 98
            if (0 < \count($errors)) {
190 6
                throw new Exception('Query failed: '.json_encode($errors));
191
            }
192
        }
193
194 96
        $queryType = $infos['query']['type'];
195 96
        $validTypes = ['select', 'ask', 'describe', 'construct', 'load', 'insert', 'delete', 'dump'];
196 96
        if (!\in_array($queryType, $validTypes)) {
197
            throw new Exception('Unsupported query type "'.$queryType.'"');
198
        }
199
200 96
        $cls = match ($queryType) {
201 3
            'ask' => AskQueryHandler::class,
202 3
            'construct' => ConstructQueryHandler::class,
203 3
            'describe' => DescribeQueryHandler::class,
204 8
            'delete' => DeleteQueryHandler::class,
205 91
            'insert' => InsertQueryHandler::class,
206 86
            'select' => SelectQueryHandler::class,
207 96
        };
208
209 96
        if (empty($cls)) {
210
            throw new Exception('Inalid query $type given.');
211
        }
212
213 96
        if ('insert' == $queryType) {
214
            // reuse InsertQueryHandler because its max term ID
215
            // when it gets recreated everytime the max term ID start by 1 and we get:
216
            // Integrity constraint violation: 19 UNIQUE constraint failed: id2val.id
217 91
            $queryHandler = $this->insertQueryHandler;
218
        } else {
219 92
            $queryHandlerLogger = $this->loggerPool->createNewLogger('QueryHandler');
220 92
            $queryHandler = new $cls($this, $queryHandlerLogger);
221
        }
222
223 96
        $queryResult = $queryHandler->runQuery($infos);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $queryResult is correct as $queryHandler->runQuery($infos) targeting sweetrdf\InMemoryStoreSq...ueryHandler::runQuery() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
224
225 96
        $result = null;
226 96
        if ('raw' == $format) {
227
            // use plain old ARC2 format which is an array of arrays
228 96
            $result = ['query_type' => $queryType, 'result' => $queryResult];
229 2
        } elseif ('instances' == $format) {
230
            // use rdfInstance instance(s) to represent result entries
231 2
            if (\is_array($queryResult)) {
0 ignored issues
show
introduced by
The condition is_array($queryResult) is always false.
Loading history...
232 1
                $variables = $queryResult['variables'];
233
234 1
                foreach ($queryResult['rows'] as $row) {
235 1
                    $resultEntry = [];
236 1
                    foreach ($variables as $variable) {
237 1
                        if ('uri' == $row[$variable.' type']) {
238 1
                            $resultEntry[$variable] = $this->dataFactory->namedNode($row[$variable]);
239 1
                        } elseif ('bnode' == $row[$variable.' type']) {
240 1
                            $resultEntry[$variable] = $this->dataFactory->blankNode($row[$variable]);
241 1
                        } elseif ('literal' == $row[$variable.' type']) {
242 1
                            $resultEntry[$variable] = $this->dataFactory->literal(
243 1
                                $row[$variable],
244 1
                                $row[$variable.' lang'] ?? null,
245 1
                                $row[$variable.' datatype'] ?? null
246 1
                            );
247
                        } else {
248
                            throw new Exception('Invalid type given: '.$row[$variable.' type']);
249
                        }
250
                    }
251 1
                    $result[] = $resultEntry;
252
                }
253
            } else {
254 1
                $result = $this->dataFactory->literal($queryResult);
255
            }
256
        }
257
258 96
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result could return the type null which is incompatible with the type-hinted return array|rdfInterface\LiteralInterface. Consider adding an additional type-check to rule them out.
Loading history...
259
    }
260
}
261