Completed
Push — master ( 93cace...d12a55 )
by Richard
06:16
created

IndexDB   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 95.03%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 9
dl 0
loc 232
ccs 172
cts 181
cp 0.9503
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A write_index() 0 5 1
A read_index() 0 6 1
A build_graph_index_db() 0 3 1
A serialize_nodes() 0 18 3
A serialize_relations() 0 18 4
A prepare_insert_statements() 0 14 1
A insert_properties() 0 5 2
A insert_property() 0 12 3
A deserialize_nodes_to() 0 11 2
A deserialize_relations_to() 0 16 4
A select_properties() 0 16 3
A init_database_schema() 0 71 1
1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 *
5
 * Copyright (c) 2016, 2015 Richard Klees <[email protected]>
6
 *
7
 * This software is licensed under The MIT License. You should have received
8
 * a copy of the license along with the code.
9
 */
10
11
namespace Lechimp\Dicto\App;
12
13
use Lechimp\Dicto\Graph;
14
use Doctrine\DBAL\Schema;
15
use Doctrine\DBAL\Types\Type;
16
use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer;
17
use Doctrine\DBAL\Statement;
18
19
class IndexDB extends DB {
20
    protected $insert_per_transaction = 1000;
21
22
    /**
23
     * Write the index to the database.
24
     *
25
     * @param   Graph\IndexDB   $index
26
     * @return  null
27
     */
28 1
    public function write_index(Graph\IndexDB $index) {
29 1
        $this->prepare_insert_statements();
30 1
        $max_id = $this->serialize_nodes($index);
31 1
        $this->serialize_relations($index, $max_id);
32 1
    }
33
34
    /**
35
     * Read the index from the database.
36
     *
37
     * @return  Graph\IndexDB   $index
38
     */
39 1
    public function read_index() {
40 1
        $index = $this->build_graph_index_db();
41 1
        $this->deserialize_nodes_to($index);
42 1
        $this->deserialize_relations_to($index);
43 1
        return $index;
44
    }
45
46
    protected function build_graph_index_db() {
47
        return new Graph\IndexDB();
48
    }
49
50
    // WRITE
51
52 1
    protected function serialize_nodes(Graph\IndexDB $index) {
53 1
        $max_id = 0;
54 1
        $this->connection->beginTransaction();
55 1
        $count = 0;
56 1
        foreach ($index->nodes() as $node) {
57 1
            $count++;
58 1
            $id = $node->id();
59 1
            $this->insert_entity_stmt->execute([$node->id(), $node->type()]);
60 1
            $max_id = max($max_id, $id);
61 1
            $this->insert_properties($id, $node);
62 1
            if ($count >= $this->insert_per_transaction) {
63
                $this->connection->commit();
64
                $this->connection->beginTransaction();
65
            }
66 1
        }
67 1
        $this->connection->commit();
68 1
        return $max_id;
69
    }
70
71 1
    protected function serialize_relations(Graph\IndexDB $index, $id) {
72 1
        $this->connection->beginTransaction();
73 1
        $count = 0;
74 1
        foreach ($index->nodes() as $node) {
75 1
            foreach ($node->relations() as $relation) {
76 1
                $count++;
77 1
                $id++;
78 1
                $this->insert_entity_stmt->execute([$id, $relation->type()]);
79 1
                $this->insert_properties($id, $relation);
80 1
                $this->insert_relation_stmt->execute([$node->id(), $id, $relation->target()->id()]);
81 1
                if ($count >= $this->insert_per_transaction) {
82
                    $this->connection->commit();
83
                    $this->connection->beginTransaction();
84
                }
85 1
            }
86 1
        }
87 1
        $this->connection->commit();
88 1
    }
89
90
    protected $insert_entity_stmt;
91
    protected $insert_property_stmt;
92
    protected $insert_relation_stmt;
93
94 1
    protected function prepare_insert_statements() {
95 1
        $this->insert_entity_stmt = $this->connection->prepare
96 1
            ( "INSERT INTO entities (id, type)\n"
97
            . "VALUES (?, ?)"
98 1
            );
99 1
        $this->insert_property_stmt = $this->connection->prepare
100 1
            ( "INSERT INTO properties (entity_id, key, is_entity, value)\n"
101
            . "VALUES (?, ?, ?, ?)"
102 1
            );
103 1
        $this->insert_relation_stmt = $this->connection->prepare
104 1
            ( "INSERT INTO relations (source_id, entity_id, target_id)\n"
105
            . "VALUES (?, ?, ?)"
106 1
            );
107 1
    }
108
109 1
    protected function insert_properties($id, Graph\Entity $e) {
110 1
        foreach ($e->properties() as $key => $value) {
111 1
            $this->insert_property($id, $key, $value);
112 1
        }
113 1
    }
114
115 1
    protected function insert_property($entity_id, $key, $value) {
116 1
        $is_entity = false;
117 1
        if ($value instanceof Graph\Node) {
118 1
            $is_entity = true;
119 1
            $value = $value->id();
120 1
        }
121 1
        if ($value instanceof Graph\Relation) {
122
            throw new \LogicException("Can't serialize Relation properties (NYI!)");
123
        }
124 1
        $value = serialize($value);
125 1
        $this->insert_property_stmt->execute([$entity_id, $key, $is_entity, $value]);
126 1
    }
127
128
    // READ
129
130 1
    protected function deserialize_nodes_to(Graph\IndexDB $index) {
131 1
        $res = $this->connection->executeQuery
132 1
            ( "SELECT entities.id, entities.type FROM entities\n"
133
            . "LEFT JOIN relations ON entities.id = relations.entity_id\n"
134 1
            . "WHERE relations.source_id IS NULL"
135 1
            );
136 1
        while ($row = $res->fetch()) {
137 1
            $properties = $this->select_properties($row["id"], $index);
138 1
            $index->create_node($row["type"], $properties);
139 1
        }
140 1
    }
141
142 1
    protected function deserialize_relations_to(Graph\IndexDB $index) {
143 1
        $res = $this->connection->executeQuery
144 1
            ( "SELECT source_id, entity_id, target_id, type FROM relations\n"
145
            . "JOIN entities ON entities.id = relations.entity_id\n"
146 1
            . "ORDER BY source_id, entity_id"
147 1
            );
148 1
        $node = null;
149 1
        while ($row = $res->fetch()) {
150 1
            if ($node === null || $node->id() != $row["source_id"]) {
151 1
                $node = $index->node((int)$row["source_id"]);
152 1
            }
153 1
            $other = $index->node((int)$row["target_id"]);
154 1
            $properties = $this->select_properties($row["entity_id"], $index);
155 1
            $node->add_relation($row["type"], $properties, $other);
156 1
        }
157 1
    }
158
159
160 1
    protected function select_properties($id, $index) {
161 1
        $id = (int)$id;
162 1
        $res = $this->connection->executeQuery
163 1
            ( "SELECT key, is_entity, value FROM properties\n"
164 1
            . "WHERE entity_id = $id"
165 1
            );
166 1
        $props = [];
167 1
        while ($row = $res->fetch()) {
168 1
            $value = unserialize($row["value"]);
169 1
            if ($row["is_entity"]) {
170 1
                $value = $index->node($value);
171 1
            }
172 1
            $props[$row["key"]] = $value;
173 1
        }
174 1
        return $props;
175
    }
176
177
    // INIT
178
179 1
    public function init_database_schema() {
180 1
        $schema = new Schema\Schema();
181
182 1
        $entity_table = $schema->createTable("entities");
183 1
        $entity_table->addColumn
184 1
            ("id", "integer"
185 1
            , ["notnull" => true, "unsigned" => true]
186 1
            );
187 1
        $entity_table->addColumn
188 1
            ( "type", "string"
189 1
            , ["notnull" => true]
190 1
            );
191 1
        $entity_table->setPrimaryKey(["id"]);
192
193 1
        $property_table = $schema->createTable("properties");
194 1
        $property_table->addColumn
195 1
            ("entity_id", "integer"
196 1
            , ["notnull" => true, "unsigned" => true]
197 1
            );
198 1
        $property_table->addColumn
199 1
            ( "key", "string"
200 1
            , ["notnull" => true]
201 1
            );
202 1
        $property_table->addColumn
203 1
            ( "is_entity", "boolean"
204 1
            , ["notnull" => true]
205 1
            );
206 1
        $property_table->addColumn
207 1
            ( "value", "string"
208 1
            , ["notnull" => true]
209 1
            );
210 1
        $property_table->addForeignKeyConstraint
211 1
            ( $entity_table
212 1
            , array("entity_id")
213 1
            , array("id")
214 1
            );
215 1
        $property_table->setPrimaryKey(["entity_id", "key"]);
216
217 1
        $relation_table = $schema->createTable("relations");
218 1
        $relation_table->addColumn
219 1
            ( "source_id", "integer"
220 1
            , ["notnull" => true, "unsigned" => true]
221 1
            );
222 1
        $relation_table->addColumn
223 1
            ( "entity_id", "integer"
224 1
            , ["notnull" => true, "unsigned" => true]
225 1
            );
226 1
        $relation_table->addColumn
227 1
            ( "target_id", "integer"
228 1
            , ["notnull" => true, "unsigned" => true]
229 1
            );
230 1
        $relation_table->addForeignKeyConstraint
231 1
            ( $entity_table
232 1
            , array("source_id")
233 1
            , array("id")
234 1
            );
235 1
        $relation_table->addForeignKeyConstraint
236 1
            ( $entity_table
237 1
            , array("entity_id")
238 1
            , array("id")
239 1
            );
240 1
        $relation_table->addForeignKeyConstraint
241 1
            ( $entity_table
242 1
            , array("target_id")
243 1
            , array("id")
244 1
            );
245 1
        $relation_table->setPrimaryKey(["entity_id"]);
246
247 1
        $sync = new SingleDatabaseSynchronizer($this->connection);
248 1
        $sync->createSchema($schema);
249 1
    }
250
}
251