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
|
|
|
|
19
|
|
|
private $repository; |
20
|
|
|
|
21
|
|
|
public function __construct(Mapper $mapper, $id, $name) |
22
|
|
|
{ |
23
|
|
|
$this->mapper = $mapper; |
24
|
|
|
$this->id = $id; |
25
|
|
|
$this->name = $name; |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
public function addProperties($config) |
29
|
|
|
{ |
30
|
|
|
foreach($config as $name => $type) { |
31
|
|
|
$this->addProperty($name, $type); |
32
|
|
|
} |
33
|
|
|
return $this; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
public function addProperty($name, $type) |
37
|
|
|
{ |
38
|
|
|
$format = $this->getFormat(); |
39
|
|
|
foreach($format as $field) { |
40
|
|
|
if($field['name'] == $name) { |
41
|
|
|
throw new Exception("Property $name exists"); |
42
|
|
|
} |
43
|
|
|
} |
44
|
|
|
$format[] = compact('name', 'type'); |
45
|
|
|
$this->format = $format; |
46
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]); |
47
|
|
|
|
48
|
|
|
$this->parseFormat(); |
49
|
|
|
|
50
|
|
|
return $this; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function removeProperty($name) |
54
|
|
|
{ |
55
|
|
|
$format = $this->getFormat(); |
56
|
|
|
$last = array_pop($format); |
57
|
|
|
if($last['name'] != $name) { |
58
|
|
|
throw new Exception("Remove only last property"); |
59
|
|
|
} |
60
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id]:format(...)", [$format]); |
61
|
|
|
$this->format = $format; |
62
|
|
|
|
63
|
|
|
$this->parseFormat(); |
64
|
|
|
|
65
|
|
|
return $this; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
public function removeIndex($name) |
69
|
|
|
{ |
70
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id].index.$name:drop()"); |
71
|
|
|
$this->indexes = []; |
72
|
|
|
$this->mapper->getRepository('_index')->flushCache(); |
73
|
|
|
|
74
|
|
|
return $this; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
public function addIndex($config) |
78
|
|
|
{ |
79
|
|
|
return $this->createIndex($config); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
public function createIndex($config) |
83
|
|
|
{ |
84
|
|
|
|
85
|
|
|
if(!is_array($config)) { |
86
|
|
|
$config = ['fields' => $config]; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
if(!array_key_exists('fields', $config)) { |
91
|
|
|
if(array_values($config) != $config) { |
92
|
|
|
throw new Exception("Invalid index configuration"); |
93
|
|
|
} |
94
|
|
|
$config = [ |
95
|
|
|
'fields' => $config |
96
|
|
|
]; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
if(!is_array($config['fields'])) { |
100
|
|
|
$config['fields'] = [$config['fields']]; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$options = [ |
104
|
|
|
'parts' => [] |
105
|
|
|
]; |
106
|
|
|
|
107
|
|
|
foreach($config as $k => $v) { |
108
|
|
|
if($k != 'name' && $k != 'fields') { |
109
|
|
|
$options[$k] = $v; |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
foreach($config['fields'] as $property) { |
114
|
|
|
if(!$this->getPropertyType($property)) { |
115
|
|
|
throw new Exception("Unknown property $property", 1); |
116
|
|
|
} |
117
|
|
|
$options['parts'][] = $this->getPropertyIndex($property)+1; |
118
|
|
|
$options['parts'][] = $this->getPropertyType($property); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$name = array_key_exists('name', $config) ? $config['name'] : implode('_', $config['fields']); |
122
|
|
|
|
123
|
|
|
$this->mapper->getClient()->evaluate("box.space[$this->id]:create_index('$name', ...)", [$options]); |
124
|
|
|
$this->indexes = []; |
125
|
|
|
|
126
|
|
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
public function isSpecial() |
130
|
|
|
{ |
131
|
|
|
return $this->id == 280 || $this->id == 288; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
public function getId() |
135
|
|
|
{ |
136
|
|
|
return $this->id; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
public function getFormat() |
140
|
|
|
{ |
141
|
|
|
if(!$this->format) { |
|
|
|
|
142
|
|
|
if($this->isSpecial()) { |
143
|
|
|
$this->format = $this->mapper->getClient() |
144
|
|
|
->getSpace(280)->select([$this->id])->getData()[0][6]; |
145
|
|
|
|
146
|
|
|
} else { |
147
|
|
|
$this->format = $this->mapper->findOne('_space', ['id' => $this->id])->format; |
148
|
|
|
} |
149
|
|
|
if(!$this->format) { |
150
|
|
|
$this->format = []; |
151
|
|
|
} |
152
|
|
|
$this->parseFormat(); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
return $this->format; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
public function getMapper() |
159
|
|
|
{ |
160
|
|
|
return $this->mapper; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
public function getName() |
164
|
|
|
{ |
165
|
|
|
return $this->name; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
private function parseFormat() |
169
|
|
|
{ |
170
|
|
|
$this->formatTypesHash = []; |
171
|
|
|
$this->formatNamesHash = []; |
172
|
|
|
foreach($this->format as $key => $row) { |
173
|
|
|
$this->formatTypesHash[$row['name']] = $row['type']; |
174
|
|
|
$this->formatNamesHash[$row['name']] = $key; |
175
|
|
|
} |
176
|
|
|
return $this; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
public function hasProperty($name) |
180
|
|
|
{ |
181
|
|
|
$this->getFormat(); |
182
|
|
|
return array_key_exists($name, $this->formatNamesHash); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
public function getPropertyType($name) |
186
|
|
|
{ |
187
|
|
|
if(!$this->hasProperty($name)) { |
188
|
|
|
throw new Exception("No property $name"); |
189
|
|
|
} |
190
|
|
|
return $this->formatTypesHash[$name]; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
public function getPropertyIndex($name) |
194
|
|
|
{ |
195
|
|
|
if(!$this->hasProperty($name)) { |
196
|
|
|
throw new Exception("No property $name"); |
197
|
|
|
} |
198
|
|
|
return $this->formatNamesHash[$name]; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public function getIndexes() |
202
|
|
|
{ |
203
|
|
|
if(!$this->indexes) { |
|
|
|
|
204
|
|
|
if($this->isSpecial()) { |
205
|
|
|
$this->indexes = []; |
206
|
|
|
$indexTuples = $this->mapper->getClient()->getSpace(288)->select([$this->id])->getData(); |
207
|
|
|
$indexFormat = $this->mapper->getSchema()->getSpace(288)->getFormat(); |
208
|
|
|
foreach($indexTuples as $tuple) { |
209
|
|
|
$instance = (object) []; |
210
|
|
|
foreach($indexFormat as $index => $format) { |
211
|
|
|
$instance->{$format['name']} = $tuple[$index]; |
212
|
|
|
} |
213
|
|
|
$this->indexes[] = $instance; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
} else { |
217
|
|
|
$this->indexes = $this->mapper->find('_index', ['id' => $this->id]); |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
return $this->indexes; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
public function castIndex($params) |
224
|
|
|
{ |
225
|
|
|
$keys = array_keys($params); |
|
|
|
|
226
|
|
|
|
227
|
|
|
$keys = []; |
228
|
|
|
foreach($params as $name => $value) { |
229
|
|
|
$keys[] = $this->getPropertyIndex($name); |
230
|
|
|
} |
231
|
|
|
if($keys == [0]) { |
232
|
|
|
// primary index |
233
|
|
|
return 0; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
// equals |
237
|
|
|
foreach($this->getIndexes() as $index) { |
238
|
|
|
$equals = false; |
239
|
|
|
if(count($keys) == count($index->parts)) { |
240
|
|
|
// same length |
241
|
|
|
$equals = true; |
242
|
|
|
foreach($index->parts as $part) { |
243
|
|
|
$equals = $equals && in_array($part[0], $keys); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if($equals) { |
248
|
|
|
return $index->iid; |
249
|
|
|
} |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
// index part |
253
|
|
|
foreach($this->getIndexes() as $index) { |
254
|
|
|
$partial = []; |
255
|
|
|
foreach($index->parts as $n => $part) { |
256
|
|
|
if(!array_key_exists($n, $keys)) { |
257
|
|
|
break; |
258
|
|
|
} |
259
|
|
|
if($keys[$n] != $part[0]) { |
260
|
|
|
break; |
261
|
|
|
} |
262
|
|
|
$partial[] = $keys[$n]; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
if(count($partial) == count($keys)) { |
266
|
|
|
return $index->iid; |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
throw new Exception("No index"); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
public function getIndexValues($indexId, $params) |
274
|
|
|
{ |
275
|
|
|
$index = $this->getIndexes()[$indexId]; |
276
|
|
|
$format = $this->getFormat(); |
277
|
|
|
|
278
|
|
|
$values = []; |
279
|
|
|
foreach($index->parts as $part) { |
280
|
|
|
$name = $format[$part[0]]['name']; |
281
|
|
|
if(!array_key_exists($name, $params)) { |
282
|
|
|
break; |
283
|
|
|
} |
284
|
|
|
$values[] = $this->mapper->getSchema()->formatValue($part[1], $params[$name]); |
285
|
|
|
} |
286
|
|
|
return $values; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
public function getPrimaryIndex() |
290
|
|
|
{ |
291
|
|
|
$indexes = $this->getIndexes(); |
292
|
|
|
if(!count($indexes)) { |
293
|
|
|
throw new Exception("No primary index"); |
294
|
|
|
} |
295
|
|
|
return $indexes[0]; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
public function getTupleKey($tuple) |
299
|
|
|
{ |
300
|
|
|
$key = []; |
301
|
|
|
foreach($this->getPrimaryIndex()->parts as $part) { |
302
|
|
|
$key[] = $tuple[$part[0]]; |
303
|
|
|
} |
304
|
|
|
return count($key) == 1 ? $key[0] : implode(':', $key); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
public function getInstanceKey($instance) |
308
|
|
|
{ |
309
|
|
|
|
310
|
|
|
$key = []; |
311
|
|
|
|
312
|
|
|
foreach($this->getPrimaryIndex()->parts as $part) { |
313
|
|
|
$name = $this->getFormat()[$part[0]]['name']; |
314
|
|
|
if(!property_exists($instance, $name)) { |
315
|
|
|
throw new Exception("Field $name is undefined", 1); |
316
|
|
|
} |
317
|
|
|
$key[] = $instance->$name; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
return count($key) == 1 ? $key[0] : implode(':', $key); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
public function getRepository() |
324
|
|
|
{ |
325
|
|
|
$class = Repository::class; |
326
|
|
|
foreach($this->mapper->getPlugins() as $plugin) { |
327
|
|
|
$repositoryClass = $plugin->getRepositoryClass($this); |
328
|
|
|
if($repositoryClass) { |
329
|
|
|
if($class != Repository::class) { |
330
|
|
|
throw new Exception('Repository class override'); |
331
|
|
|
} |
332
|
|
|
$class = $repositoryClass; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
return $this->repository ?: $this->repository = new $class($this); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.