|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Tarantool\Mapper; |
|
4
|
|
|
|
|
5
|
|
|
use Exception; |
|
6
|
|
|
|
|
7
|
|
|
class Space |
|
8
|
|
|
{ |
|
9
|
|
|
private $mapper; |
|
10
|
|
|
|
|
11
|
|
|
private $id; |
|
12
|
|
|
private $name; |
|
13
|
|
|
private $format; |
|
14
|
|
|
private $indexes; |
|
15
|
|
|
|
|
16
|
|
|
private $formatNamesHash = []; |
|
17
|
|
|
private $formatTypesHash = []; |
|
18
|
|
|
private $formatReferences = []; |
|
19
|
|
|
|
|
20
|
|
|
private $repository; |
|
21
|
|
|
|
|
22
|
|
|
public function __construct(Mapper $mapper, $id, $name, $meta = null) |
|
23
|
|
|
{ |
|
24
|
|
|
$this->mapper = $mapper; |
|
25
|
|
|
$this->id = $id; |
|
26
|
|
|
$this->name = $name; |
|
27
|
|
|
|
|
28
|
|
|
if ($meta) { |
|
29
|
|
|
foreach ($meta as $key => $value) { |
|
30
|
|
|
$this->$key = $value; |
|
31
|
|
|
} |
|
32
|
|
|
} |
|
33
|
|
|
} |
|
34
|
|
|
|
|
35
|
|
|
public function addProperties($config) |
|
36
|
|
|
{ |
|
37
|
|
|
foreach ($config as $name => $type) { |
|
38
|
|
|
$this->addProperty($name, $type); |
|
39
|
|
|
} |
|
40
|
|
|
return $this; |
|
41
|
|
|
} |
|
42
|
|
|
|
|
43
|
|
|
public function addProperty($name, $type, $reference = null) |
|
44
|
|
|
{ |
|
45
|
|
|
$format = $this->getFormat(); |
|
46
|
|
|
foreach ($format as $field) { |
|
47
|
|
|
if ($field['name'] == $name) { |
|
48
|
|
|
throw new Exception("Property $name exists"); |
|
49
|
|
|
} |
|
50
|
|
|
} |
|
51
|
|
|
$row = compact('name', 'type'); |
|
52
|
|
|
if ($reference) { |
|
53
|
|
|
$row['reference'] = $reference; |
|
54
|
|
|
} |
|
55
|
|
|
$format[] = $row; |
|
56
|
|
|
$this->format = $format; |
|
57
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]); |
|
58
|
|
|
|
|
59
|
|
|
$this->parseFormat(); |
|
60
|
|
|
|
|
61
|
|
|
return $this; |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
public function removeProperty($name) |
|
65
|
|
|
{ |
|
66
|
|
|
$format = $this->getFormat(); |
|
67
|
|
|
$last = array_pop($format); |
|
68
|
|
|
if ($last['name'] != $name) { |
|
69
|
|
|
throw new Exception("Remove only last property"); |
|
70
|
|
|
} |
|
71
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]); |
|
72
|
|
|
$this->format = $format; |
|
73
|
|
|
|
|
74
|
|
|
$this->parseFormat(); |
|
75
|
|
|
|
|
76
|
|
|
return $this; |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
public function removeIndex($name) |
|
80
|
|
|
{ |
|
81
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id].index.$name:drop()"); |
|
82
|
|
|
$this->indexes = []; |
|
83
|
|
|
$this->mapper->getRepository('_index')->flushCache(); |
|
84
|
|
|
|
|
85
|
|
|
return $this; |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
|
|
public function addIndex($config) |
|
89
|
|
|
{ |
|
90
|
|
|
return $this->createIndex($config); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
public function createIndex($config) |
|
94
|
|
|
{ |
|
95
|
|
|
if (!is_array($config)) { |
|
96
|
|
|
$config = ['fields' => $config]; |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
|
|
100
|
|
|
if (!array_key_exists('fields', $config)) { |
|
101
|
|
|
if (array_values($config) != $config) { |
|
102
|
|
|
throw new Exception("Invalid index configuration"); |
|
103
|
|
|
} |
|
104
|
|
|
$config = [ |
|
105
|
|
|
'fields' => $config |
|
106
|
|
|
]; |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
if (!is_array($config['fields'])) { |
|
110
|
|
|
$config['fields'] = [$config['fields']]; |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
$options = [ |
|
114
|
|
|
'parts' => [] |
|
115
|
|
|
]; |
|
116
|
|
|
|
|
117
|
|
|
foreach ($config as $k => $v) { |
|
118
|
|
|
if ($k != 'name' && $k != 'fields') { |
|
119
|
|
|
$options[$k] = $v; |
|
120
|
|
|
} |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
View Code Duplication |
foreach ($config['fields'] as $property) { |
|
|
|
|
|
|
124
|
|
|
if (!$this->getPropertyType($property)) { |
|
125
|
|
|
throw new Exception("Unknown property $property", 1); |
|
126
|
|
|
} |
|
127
|
|
|
$options['parts'][] = $this->getPropertyIndex($property)+1; |
|
128
|
|
|
$options['parts'][] = $this->getPropertyType($property); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
$name = array_key_exists('name', $config) ? $config['name'] : implode('_', $config['fields']); |
|
132
|
|
|
|
|
133
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id]:create_index('$name', ...)", [$options]); |
|
134
|
|
|
$this->indexes = []; |
|
135
|
|
|
|
|
136
|
|
|
$this->mapper->getSchema()->getSpace('_index')->getRepository()->flushCache(); |
|
137
|
|
|
|
|
138
|
|
|
return $this; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
public function isSpecial() |
|
142
|
|
|
{ |
|
143
|
|
|
return $this->id == 280 || $this->id == 288; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
public function getId() |
|
147
|
|
|
{ |
|
148
|
|
|
return $this->id; |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
public function getTupleMap() |
|
152
|
|
|
{ |
|
153
|
|
|
$reverse = []; |
|
154
|
|
|
foreach ($this->getFormat() as $i => $field) { |
|
155
|
|
|
$reverse[$field['name']] = $i + 1; |
|
156
|
|
|
} |
|
157
|
|
|
return (object) $reverse; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
public function getFormat() |
|
161
|
|
|
{ |
|
162
|
|
|
if (!$this->format) { |
|
|
|
|
|
|
163
|
|
|
if ($this->isSpecial()) { |
|
164
|
|
|
$this->format = $this->mapper->getClient() |
|
165
|
|
|
->getSpace(280)->select([$this->id])->getData()[0][6]; |
|
166
|
|
|
} else { |
|
167
|
|
|
$this->format = $this->mapper->findOne('_space', ['id' => $this->id])->format; |
|
168
|
|
|
} |
|
169
|
|
|
if (!$this->format) { |
|
170
|
|
|
$this->format = []; |
|
171
|
|
|
} |
|
172
|
|
|
$this->parseFormat(); |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
return $this->format; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
public function getMapper() |
|
179
|
|
|
{ |
|
180
|
|
|
return $this->mapper; |
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
public function getName() |
|
184
|
|
|
{ |
|
185
|
|
|
return $this->name; |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
private function parseFormat() |
|
189
|
|
|
{ |
|
190
|
|
|
$this->formatTypesHash = []; |
|
191
|
|
|
$this->formatNamesHash = []; |
|
192
|
|
|
$this->formatReferences = []; |
|
193
|
|
|
foreach ($this->format as $key => $row) { |
|
194
|
|
|
$this->formatTypesHash[$row['name']] = $row['type']; |
|
195
|
|
|
$this->formatNamesHash[$row['name']] = $key; |
|
196
|
|
|
if (array_key_exists('reference', $row)) { |
|
197
|
|
|
$this->formatReferences[$row['name']] = $row['reference']; |
|
198
|
|
|
} |
|
199
|
|
|
} |
|
200
|
|
|
return $this; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
public function getReference($name) |
|
204
|
|
|
{ |
|
205
|
|
|
return $this->isReference($name) ? $this->formatReferences[$name] : null; |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
public function isReference($name) |
|
209
|
|
|
{ |
|
210
|
|
|
return array_key_exists($name, $this->formatReferences); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
public function hasProperty($name) |
|
214
|
|
|
{ |
|
215
|
|
|
$this->getFormat(); |
|
216
|
|
|
return array_key_exists($name, $this->formatNamesHash); |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
public function getMeta() |
|
220
|
|
|
{ |
|
221
|
|
|
$this->getFormat(); |
|
222
|
|
|
$this->getIndexes(); |
|
223
|
|
|
|
|
224
|
|
|
return [ |
|
225
|
|
|
'formatNamesHash' => $this->formatNamesHash, |
|
226
|
|
|
'formatTypesHash' => $this->formatTypesHash, |
|
227
|
|
|
'indexes' => $this->indexes, |
|
228
|
|
|
'format' => $this->format, |
|
229
|
|
|
]; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
public function getPropertyType($name) |
|
233
|
|
|
{ |
|
234
|
|
|
if (!$this->hasProperty($name)) { |
|
235
|
|
|
throw new Exception("No property $name"); |
|
236
|
|
|
} |
|
237
|
|
|
return $this->formatTypesHash[$name]; |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
public function getPropertyIndex($name) |
|
241
|
|
|
{ |
|
242
|
|
|
if (!$this->hasProperty($name)) { |
|
243
|
|
|
throw new Exception("No property $name"); |
|
244
|
|
|
} |
|
245
|
|
|
return $this->formatNamesHash[$name]; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
public function getIndexes() |
|
249
|
|
|
{ |
|
250
|
|
|
if (!$this->indexes) { |
|
|
|
|
|
|
251
|
|
|
if ($this->isSpecial()) { |
|
252
|
|
|
$this->indexes = []; |
|
253
|
|
|
$indexTuples = $this->mapper->getClient()->getSpace(288)->select([$this->id])->getData(); |
|
254
|
|
|
$indexFormat = $this->mapper->getSchema()->getSpace(288)->getFormat(); |
|
255
|
|
|
foreach ($indexTuples as $tuple) { |
|
|
|
|
|
|
256
|
|
|
$instance = []; |
|
257
|
|
|
foreach ($indexFormat as $index => $format) { |
|
258
|
|
|
$instance[$format['name']] = $tuple[$index]; |
|
259
|
|
|
} |
|
260
|
|
|
$this->indexes[] = $instance; |
|
261
|
|
|
} |
|
262
|
|
|
} else { |
|
263
|
|
|
$indexes = $this->mapper->find('_index', ['id' => $this->id]); |
|
264
|
|
|
$this->indexes = []; |
|
265
|
|
|
foreach ($indexes as $index) { |
|
266
|
|
|
$index = get_object_vars($index); |
|
267
|
|
|
foreach ($index as $key => $value) { |
|
268
|
|
|
if (is_object($value)) { |
|
269
|
|
|
unset($index[$key]); |
|
270
|
|
|
} |
|
271
|
|
|
} |
|
272
|
|
|
$this->indexes[] = $index; |
|
273
|
|
|
} |
|
274
|
|
|
} |
|
275
|
|
|
} |
|
276
|
|
|
return $this->indexes; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
public function castIndex($params, $suppressException = false) |
|
280
|
|
|
{ |
|
281
|
|
|
if (!count($this->getIndexes())) { |
|
282
|
|
|
return; |
|
283
|
|
|
} |
|
284
|
|
|
$keys = array_keys($params); |
|
|
|
|
|
|
285
|
|
|
|
|
286
|
|
|
$keys = []; |
|
287
|
|
|
foreach ($params as $name => $value) { |
|
288
|
|
|
$keys[] = $this->getPropertyIndex($name); |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
// equals |
|
292
|
|
|
foreach ($this->getIndexes() as $index) { |
|
293
|
|
|
$equals = false; |
|
294
|
|
|
if (count($keys) == count($index['parts'])) { |
|
295
|
|
|
// same length |
|
296
|
|
|
$equals = true; |
|
297
|
|
|
foreach ($index['parts'] as $part) { |
|
298
|
|
|
$equals = $equals && in_array($part[0], $keys); |
|
299
|
|
|
} |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
if ($equals) { |
|
303
|
|
|
return $index['iid']; |
|
304
|
|
|
} |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
// index part |
|
308
|
|
|
foreach ($this->getIndexes() as $index) { |
|
309
|
|
|
$partial = []; |
|
310
|
|
|
foreach ($index['parts'] as $n => $part) { |
|
311
|
|
|
if (!array_key_exists($n, $keys)) { |
|
312
|
|
|
break; |
|
313
|
|
|
} |
|
314
|
|
|
if ($keys[$n] != $part[0]) { |
|
315
|
|
|
break; |
|
316
|
|
|
} |
|
317
|
|
|
$partial[] = $keys[$n]; |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
if (count($partial) == count($keys)) { |
|
321
|
|
|
return $index['iid']; |
|
322
|
|
|
} |
|
323
|
|
|
} |
|
324
|
|
|
|
|
325
|
|
|
if (!$suppressException) { |
|
326
|
|
|
throw new Exception("No index"); |
|
327
|
|
|
} |
|
328
|
|
|
} |
|
329
|
|
|
|
|
330
|
|
|
public function getIndexValues($indexId, $params) |
|
331
|
|
|
{ |
|
332
|
|
|
$index = null; |
|
333
|
|
|
foreach ($this->getIndexes() as $candidate) { |
|
334
|
|
|
if ($candidate['iid'] == $indexId) { |
|
335
|
|
|
$index = $candidate; |
|
336
|
|
|
break; |
|
337
|
|
|
} |
|
338
|
|
|
} |
|
339
|
|
|
if (!$index) { |
|
340
|
|
|
throw new Exception("Undefined index: $indexId"); |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
$format = $this->getFormat(); |
|
344
|
|
|
$values = []; |
|
345
|
|
|
foreach ($index['parts'] as $part) { |
|
346
|
|
|
$name = $format[$part[0]]['name']; |
|
347
|
|
|
if (!array_key_exists($name, $params)) { |
|
348
|
|
|
break; |
|
349
|
|
|
} |
|
350
|
|
|
$values[] = $this->mapper->getSchema()->formatValue($part[1], $params[$name]); |
|
351
|
|
|
} |
|
352
|
|
|
return $values; |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
public function getPrimaryIndex() |
|
356
|
|
|
{ |
|
357
|
|
|
$indexes = $this->getIndexes(); |
|
358
|
|
|
if (!count($indexes)) { |
|
359
|
|
|
throw new Exception("No primary index"); |
|
360
|
|
|
} |
|
361
|
|
|
return $indexes[0]; |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
public function getTupleKey($tuple) |
|
365
|
|
|
{ |
|
366
|
|
|
$key = []; |
|
367
|
|
|
foreach ($this->getPrimaryIndex()['parts'] as $part) { |
|
368
|
|
|
$key[] = $tuple[$part[0]]; |
|
369
|
|
|
} |
|
370
|
|
|
return count($key) == 1 ? $key[0] : implode(':', $key); |
|
371
|
|
|
} |
|
372
|
|
|
|
|
373
|
|
|
public function getInstanceKey($instance) |
|
374
|
|
|
{ |
|
375
|
|
|
$key = []; |
|
376
|
|
|
|
|
377
|
|
View Code Duplication |
foreach ($this->getPrimaryIndex()['parts'] as $part) { |
|
|
|
|
|
|
378
|
|
|
$name = $this->getFormat()[$part[0]]['name']; |
|
379
|
|
|
if (!property_exists($instance, $name)) { |
|
380
|
|
|
throw new Exception("Field $name is undefined", 1); |
|
381
|
|
|
} |
|
382
|
|
|
$key[] = $instance->$name; |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
return count($key) == 1 ? $key[0] : implode(':', $key); |
|
386
|
|
|
} |
|
387
|
|
|
|
|
388
|
|
|
public function getRepository() |
|
389
|
|
|
{ |
|
390
|
|
|
$class = Repository::class; |
|
391
|
|
|
foreach ($this->mapper->getPlugins() as $plugin) { |
|
392
|
|
|
$repositoryClass = $plugin->getRepositoryClass($this); |
|
393
|
|
|
if ($repositoryClass) { |
|
394
|
|
|
if ($class != Repository::class) { |
|
395
|
|
|
throw new Exception('Repository class override'); |
|
396
|
|
|
} |
|
397
|
|
|
$class = $repositoryClass; |
|
398
|
|
|
} |
|
399
|
|
|
} |
|
400
|
|
|
return $this->repository ?: $this->repository = new $class($this); |
|
401
|
|
|
} |
|
402
|
|
|
|
|
403
|
|
|
public function repositoryExists() |
|
404
|
|
|
{ |
|
405
|
|
|
return !!$this->repository; |
|
406
|
|
|
} |
|
407
|
|
|
} |
|
408
|
|
|
|
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.