├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src
├── EagerLoading
│ ├── EagerLoad.php
│ ├── Loader.php
│ └── QueryBuilder.php
└── EagerLoadingTrait.php
└── tests
├── EagerLoadingTests.php
├── Models
├── AbstractModel.php
├── Bug.php
├── Manufacturer.php
├── NotSupportedRelation.php
├── Part.php
├── Purpose.php
├── Robot.php
└── RobotPart.php
├── bootstrap.php
└── schema.sql
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This package provides eager-loading support for Phalcon 1.3.* - 2.0.*.
2 | Requires PHP 5.6, for PHP 5.3 support use _php-5.3_ branch
3 |
4 | Usage
5 | -----
6 |
7 | The usage is similar to Laravel, I've implemented in a trait `with` and `load` methods, so within a model that uses that trait (`Sb\Framework\Mvc\Model\EagerLoadingTrait`) you can do:
8 |
9 | ```php
10 | parts; // $robot->__get('parts')
21 | }
22 |
23 | // Or
24 |
25 | $robot = Robot::findFirst()->load('Parts');
26 |
27 | // Equivalent to:
28 |
29 | $robot = Robot::findFirst();
30 | $robots->parts; // $robot->__get('parts')
31 |
32 | // Because Robot::find() returns a resultset, so in that case this is solved with:
33 | $robots = Loader::fromResultset(Robot::find(), 'Parts'); # Equivalent to the second example
34 |
35 | // Multiple and nested relations can be used too
36 | $robots = Robot::with('Parts', 'Foo.Bar');
37 |
38 | // And arguments can be passed to the find method
39 | $robots = Robot::with('Parts', 'Foo.Bar', ['limit' => 5]);
40 |
41 | // And constraints
42 | $robots = Robot::with(
43 | [
44 | 'Parts',
45 | 'Foo.Bar' => function (QueryBuilder $builder) {
46 | // Limit Bar
47 | $builder->limit(5);
48 | }
49 | ],
50 | [
51 | 'limit' => 5
52 | ]
53 | );
54 |
55 | // constraints with the Loader too
56 | $robots = Loader::fromResultset(Robot::find(), [
57 | 'Foo.Bar' => function (QueryBuilder $builder) {
58 | $builder->where('Bar.id > 10');
59 | }
60 | ]);
61 |
62 | ```
63 |
64 | For more examples, return types etc visit the tests folder or take a look at the code, it's quite small.
65 |
66 | License
67 | -------
68 | [The Unlicense](http://unlicense.org/)
69 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stibium/phalcon.eager-loading",
3 | "description": "Solves N+1 query problem in Phalcon Model",
4 | "license": "Unlicense",
5 | "autoload": {
6 | "psr-4": {
7 | "Sb\\Framework\\Mvc\\Model\\": "src"
8 | }
9 | },
10 | "require": {
11 | "php": ">=5.6",
12 | "ext-phalcon": "1.3 - 2.0"
13 | },
14 | "keywords": [
15 | "phalcon"
16 | ],
17 | "authors": [
18 | {
19 | "name": "Óscar Enríquez",
20 | "homepage": "http://oscar.xyz"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 | tests/EagerLoadingTests.php
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/EagerLoading/EagerLoad.php:
--------------------------------------------------------------------------------
1 | = 0;
29 | }
30 |
31 | $this->relation = $relation;
32 | $this->constraints = $constraints;
33 | $this->parent = $parent;
34 | }
35 |
36 | /**
37 | * @return null|Phalcon\Mvc\ModelInterface[]
38 | */
39 | public function getSubject() {
40 | return $this->subject;
41 | }
42 |
43 | /**
44 | * Executes each db query needed
45 | *
46 | * Note: The {$alias} property is set two times because Phalcon Model ignores
47 | * empty arrays when overloading property set.
48 | *
49 | * Also {@see https://github.com/stibiumz/phalcon.eager-loading/issues/1}
50 | *
51 | * @return $this
52 | */
53 | public function load() {
54 | if (empty ($this->parent->getSubject())) {
55 | return $this;
56 | }
57 |
58 | $relation = $this->relation;
59 |
60 | $alias = strtolower($relation->getOptions()['alias']);
61 | $relField = $relation->getFields();
62 | $relReferencedModel = $relation->getReferencedModel();
63 | $relReferencedField = $relation->getReferencedFields();
64 | $relIrModel = $relation->getIntermediateModel();
65 | $relIrField = $relation->getIntermediateFields();
66 | $relIrReferencedField = $relation->getIntermediateReferencedFields();
67 |
68 | // PHQL has problems with this slash
69 | if ($relReferencedModel[0] === '\\') {
70 | $relReferencedModel = ltrim($relReferencedModel, '\\');
71 | }
72 |
73 | $bindValues = [];
74 |
75 | foreach ($this->parent->getSubject() as $record) {
76 | $bindValues[$record->readAttribute($relField)] = TRUE;
77 | }
78 |
79 | $bindValues = array_keys($bindValues);
80 |
81 | $subjectSize = count($this->parent->getSubject());
82 | $isManyToManyForMany = FALSE;
83 |
84 | $builder = new QueryBuilder;
85 | $builder->from($relReferencedModel);
86 |
87 | if ($isThrough = $relation->isThrough()) {
88 | if ($subjectSize === 1) {
89 | // The query is for a single model
90 | $builder
91 | ->innerJoin(
92 | $relIrModel,
93 | sprintf(
94 | '[%s].[%s] = [%s].[%s]',
95 | $relIrModel,
96 | $relIrReferencedField,
97 | $relReferencedModel,
98 | $relReferencedField
99 | )
100 | )
101 | ->inWhere("[{$relIrModel}].[{$relIrField}]", $bindValues)
102 | ;
103 | }
104 | else {
105 | // The query is for many models, so it's needed to execute an
106 | // extra query
107 | $isManyToManyForMany = TRUE;
108 |
109 | $relIrValues = (new QueryBuilder)
110 | ->from($relIrModel)
111 | ->inWhere("[{$relIrModel}].[{$relIrField}]", $bindValues)
112 | ->getQuery()
113 | ->execute()
114 | ->setHydrateMode(Resultset::HYDRATE_ARRAYS)
115 | ;
116 |
117 | $bindValues = $modelReferencedModelValues = [];
118 |
119 | foreach ($relIrValues as $row) {
120 | $bindValues[$row[$relIrReferencedField]] = TRUE;
121 | $modelReferencedModelValues[$row[$relIrField]][$row[$relIrReferencedField]] = TRUE;
122 | }
123 |
124 | unset ($relIrValues, $row);
125 |
126 | $builder->inWhere("[{$relReferencedField}]", array_keys($bindValues));
127 | }
128 | }
129 | else {
130 | $builder->inWhere("[{$relReferencedField}]", $bindValues);
131 | }
132 |
133 | if ($this->constraints) {
134 | call_user_func($this->constraints, $builder);
135 | }
136 |
137 | $records = [];
138 |
139 | if ($isManyToManyForMany) {
140 | foreach ($builder->getQuery()->execute() as $record) {
141 | $records[$record->readAttribute($relReferencedField)] = $record;
142 | }
143 |
144 | foreach ($this->parent->getSubject() as $record) {
145 | $referencedFieldValue = $record->readAttribute($relField);
146 |
147 | if (isset ($modelReferencedModelValues[$referencedFieldValue])) {
148 | $referencedModels = [];
149 |
150 | foreach ($modelReferencedModelValues[$referencedFieldValue] as $idx => $_) {
151 | $referencedModels[] = $records[$idx];
152 | }
153 |
154 | $record->{$alias} = $referencedModels;
155 |
156 | if (static::$isPhalcon2) {
157 | $record->{$alias} = NULL;
158 | $record->{$alias} = $referencedModels;
159 | }
160 | }
161 | else {
162 | $record->{$alias} = NULL;
163 | $record->{$alias} = [];
164 | }
165 | }
166 |
167 | $records = array_values($records);
168 | }
169 | else {
170 | // We expect a single object or a set of it
171 | $isSingle = ! $isThrough && (
172 | $relation->getType() === Relation::HAS_ONE ||
173 | $relation->getType() === Relation::BELONGS_TO
174 | );
175 |
176 | if ($subjectSize === 1) {
177 | // Keep all records in memory
178 | foreach ($builder->getQuery()->execute() as $record) {
179 | $records[] = $record;
180 | }
181 |
182 | if ($isSingle) {
183 | $this->parent->getSubject()[0]->{$alias} = empty ($records) ? NULL : $records[0];
184 | }
185 | else {
186 | $record = $this->parent->getSubject()[0];
187 |
188 | if (empty ($records)) {
189 | $record->{$alias} = NULL;
190 | $record->{$alias} = [];
191 | }
192 | else {
193 | $record->{$alias} = $records;
194 |
195 | if (static::$isPhalcon2) {
196 | $record->{$alias} = NULL;
197 | $record->{$alias} = $records;
198 | }
199 | }
200 | }
201 | }
202 | else {
203 | $indexedRecords = [];
204 |
205 | // Keep all records in memory
206 | foreach ($builder->getQuery()->execute() as $record) {
207 | $records[] = $record;
208 |
209 | if ($isSingle) {
210 | $indexedRecords[$record->readAttribute($relReferencedField)] = $record;
211 | }
212 | else {
213 | $indexedRecords[$record->readAttribute($relReferencedField)][] = $record;
214 | }
215 | }
216 |
217 | foreach ($this->parent->getSubject() as $record) {
218 | $referencedFieldValue = $record->readAttribute($relField);
219 |
220 | if (isset ($indexedRecords[$referencedFieldValue])) {
221 | $record->{$alias} = $indexedRecords[$referencedFieldValue];
222 |
223 | if (static::$isPhalcon2 && is_array($indexedRecords[$referencedFieldValue])) {
224 | $record->{$alias} = NULL;
225 | $record->{$alias} = $indexedRecords[$referencedFieldValue];
226 | }
227 | }
228 | else {
229 | $record->{$alias} = NULL;
230 |
231 | if (! $isSingle) {
232 | $record->{$alias} = [];
233 | }
234 | }
235 | }
236 | }
237 | }
238 |
239 | $this->subject = $records;
240 |
241 | return $this;
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/EagerLoading/Loader.php:
--------------------------------------------------------------------------------
1 | mustReturnAModel = FALSE;
82 | }
83 | else {
84 | $className = get_class($from);
85 | $from = [$from];
86 |
87 | $this->mustReturnAModel = TRUE;
88 | }
89 |
90 | if ($error) {
91 | throw new \InvalidArgumentException(static::E_INVALID_SUBJECT);
92 | }
93 |
94 | $this->subject = $from;
95 | $this->subjectClassName = $className;
96 | $this->eagerLoads = ($from === NULL || empty ($arguments)) ? [] : static::parseArguments($arguments);
97 | }
98 |
99 | /**
100 | * Create and get from a mixed $subject
101 | *
102 | * @param ModelInterface|ModelInterface[]|Simple $subject
103 | * @param mixed ...$arguments
104 | * @throws \InvalidArgumentException
105 | * @return mixed
106 | */
107 | static public function from($subject, ...$arguments) {
108 | if ($subject instanceof ModelInterface) {
109 | $ret = static::fromModel($subject, ...$arguments);
110 | }
111 | else if ($subject instanceof Simple) {
112 | $ret = static::fromResultset($subject, ...$arguments);
113 | }
114 | else if (is_array($subject)) {
115 | $ret = static::fromArray($subject, ...$arguments);
116 | }
117 | else {
118 | throw new \InvalidArgumentException(static::E_INVALID_SUBJECT);
119 | }
120 |
121 | return $ret;
122 | }
123 |
124 | /**
125 | * Create and get from a Model
126 | *
127 | * @param ModelInterface $subject
128 | * @param mixed ...$arguments
129 | * @return ModelInterface
130 | */
131 | static public function fromModel(ModelInterface $subject, ...$arguments) {
132 | return (new static($subject, ...$arguments))->execute()->get();
133 | }
134 |
135 | /**
136 | * Create and get from an array
137 | *
138 | * @param ModelInterface[] $subject
139 | * @param mixed ...$arguments
140 | * @return array
141 | */
142 | static public function fromArray(array $subject, ...$arguments) {
143 | return (new static($subject, ...$arguments))->execute()->get();
144 | }
145 |
146 | /**
147 | * Create and get from a Resultset
148 | *
149 | * @param Simple $subject
150 | * @param mixed ...$arguments
151 | * @return Simple
152 | */
153 | static public function fromResultset(Simple $subject, ...$arguments) {
154 | return (new static($subject, ...$arguments))->execute()->get();
155 | }
156 |
157 | /**
158 | * @return null|ModelInterface[]|ModelInterface
159 | */
160 | public function get() {
161 | $ret = $this->subject;
162 |
163 | if (NULL !== $ret && $this->mustReturnAModel) {
164 | $ret = $ret[0];
165 | }
166 |
167 | return $ret;
168 | }
169 |
170 | /**
171 | * @return null|ModelInterface[]
172 | */
173 | public function getSubject() {
174 | return $this->subject;
175 | }
176 |
177 | /**
178 | * Parses the arguments that will be resolved to Relation instances
179 | *
180 | * @param array $arguments
181 | * @throws \InvalidArgumentException
182 | * @return array
183 | */
184 | static private function parseArguments(array $arguments) {
185 | if (empty ($arguments)) {
186 | throw new \InvalidArgumentException('Arguments can not be empty');
187 | }
188 |
189 | $relations = [];
190 |
191 | if (count($arguments) === 1 && isset ($arguments[0]) && is_array($arguments[0])) {
192 | foreach ($arguments[0] as $relationAlias => $queryConstraints) {
193 | if (is_string($relationAlias)) {
194 | $relations[$relationAlias] = is_callable($queryConstraints) ? $queryConstraints : NULL;
195 | }
196 | else {
197 | if (is_string($queryConstraints)) {
198 | $relations[$queryConstraints] = NULL;
199 | }
200 | }
201 | }
202 | }
203 | else {
204 | foreach ($arguments as $relationAlias) {
205 | if (is_string($relationAlias)) {
206 | $relations[$relationAlias] = NULL;
207 | }
208 | }
209 | }
210 |
211 | if (empty ($relations)) {
212 | throw new \InvalidArgumentException;
213 | }
214 |
215 | return $relations;
216 | }
217 |
218 | /**
219 | * @param string $relationAlias
220 | * @param null|callable $constraints
221 | * @return $this
222 | */
223 | public function addEagerLoad($relationAlias, callable $constraints = NULL) {
224 | if (! is_string($relationAlias)) {
225 | throw new \InvalidArgumentException(sprintf(
226 | '$relationAlias expects to be a string, `%s` given',
227 | gettype($relationAlias)
228 | ));
229 | }
230 |
231 | $this->eagerLoads[$relationAlias] = $constraints;
232 |
233 | return $this;
234 | }
235 |
236 | /**
237 | * Resolves the relations
238 | *
239 | * @throws \RuntimeException
240 | * @return EagerLoad[]
241 | */
242 | private function buildTree() {
243 | uksort($this->eagerLoads, 'strcmp');
244 |
245 | $di = \Phalcon\DI::getDefault();
246 | $mM = $di['modelsManager'];
247 |
248 | $eagerLoads = $resolvedRelations = [];
249 |
250 | foreach ($this->eagerLoads as $relationAliases => $queryConstraints) {
251 | $nestingLevel = 0;
252 | $relationAliases = explode('.', $relationAliases);
253 | $nestingLevels = count($relationAliases);
254 |
255 | do {
256 | do {
257 | $alias = $relationAliases[$nestingLevel];
258 | $name = join('.', array_slice($relationAliases, 0, $nestingLevel + 1));
259 | }
260 | while (isset ($eagerLoads[$name]) && ++$nestingLevel);
261 |
262 | if ($nestingLevel === 0) {
263 | $parentClassName = $this->subjectClassName;
264 | }
265 | else {
266 | $parentName = join('.', array_slice($relationAliases, 0, $nestingLevel));
267 | $parentClassName = $resolvedRelations[$parentName]->getReferencedModel();
268 |
269 | if ($parentClassName[0] === '\\') {
270 | ltrim($parentClassName, '\\');
271 | }
272 | }
273 |
274 | if (! isset ($resolvedRelations[$name])) {
275 | $mM->load($parentClassName);
276 | $relation = $mM->getRelationByAlias($parentClassName, $alias);
277 |
278 | if (! $relation instanceof Relation) {
279 | throw new \RuntimeException(sprintf(
280 | 'There is no defined relation for the model `%s` using alias `%s`',
281 | $parentClassName,
282 | $alias
283 | ));
284 | }
285 |
286 | $resolvedRelations[$name] = $relation;
287 | }
288 | else {
289 | $relation = $resolvedRelations[$name];
290 | }
291 |
292 | $relType = $relation->getType();
293 |
294 | if ($relType !== Relation::BELONGS_TO &&
295 | $relType !== Relation::HAS_ONE &&
296 | $relType !== Relation::HAS_MANY &&
297 | $relType !== Relation::HAS_MANY_THROUGH) {
298 |
299 | throw new \RuntimeException(sprintf('Unknown relation type `%s`', $relType));
300 | }
301 |
302 | if (is_array($relation->getFields()) ||
303 | is_array($relation->getReferencedFields())) {
304 |
305 | throw new \RuntimeException('Relations with composite keys are not supported');
306 | }
307 |
308 | $parent = $nestingLevel > 0 ? $eagerLoads[$parentName] : $this;
309 | $constraints = $nestingLevel + 1 === $nestingLevels ? $queryConstraints : NULL;
310 |
311 | $eagerLoads[$name] = new EagerLoad($relation, $constraints, $parent);
312 | }
313 | while (++$nestingLevel < $nestingLevels);
314 | }
315 |
316 | return $eagerLoads;
317 | }
318 |
319 | /**
320 | * @return $this
321 | */
322 | public function execute() {
323 | foreach ($this->buildTree() as $eagerLoad) {
324 | $eagerLoad->load();
325 | }
326 |
327 | return $this;
328 | }
329 |
330 | /**
331 | * Loader::execute() alias
332 | *
333 | * @return $this
334 | */
335 | public function load() {
336 | foreach ($this->buildTree() as $eagerLoad) {
337 | $eagerLoad->load();
338 | }
339 |
340 | return $this;
341 | }
342 | }
343 |
--------------------------------------------------------------------------------
/src/EagerLoading/QueryBuilder.php:
--------------------------------------------------------------------------------
1 | andWhere($conditions, $bindParams, $bindTypes);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/EagerLoadingTrait.php:
--------------------------------------------------------------------------------
1 |
9 | * request->getQuery('page', 'int') - 1) * $limit;
13 | *
14 | * $manufacturers = Manufacturer::with('Robots.Parts', [
15 | * 'limit' => [$limit, $offset]
16 | * ]);
17 | *
18 | * foreach ($manufacturers as $manufacturer) {
19 | * foreach ($manufacturer->robots as $robot) {
20 | * foreach ($robot->parts as $part) { ... }
21 | * }
22 | * }
23 | *
24 | *
25 | *
26 | * @param mixed ...$arguments
27 | * @return Phalcon\Mvc\ModelInterface[]
28 | */
29 | static public function with(...$arguments) {
30 | if (! empty ($arguments)) {
31 | $numArgs = count($arguments);
32 | $lastArg = $numArgs - 1;
33 | $parameters = NULL;
34 |
35 | if ($numArgs >= 2 && is_array($arguments[$lastArg])) {
36 | $parameters = $arguments[$lastArg];
37 |
38 | unset ($arguments[$lastArg]);
39 |
40 | if (isset ($parameters['columns'])) {
41 | throw new \LogicException('Results from database must be full models, do not use `columns` key');
42 | }
43 | }
44 | }
45 | else {
46 | throw new \BadMethodCallException(sprintf('%s requires at least one argument', __METHOD__));
47 | }
48 |
49 | $ret = static::find($parameters);
50 |
51 | if ($ret->count()) {
52 | $ret = Loader::fromResultset($ret, ...$arguments);
53 | }
54 |
55 | return $ret;
56 | }
57 |
58 | /**
59 | * Same as EagerLoadingTrait::with() for a single record
60 | *
61 | * @param mixed ...$arguments
62 | * @return false|Phalcon\Mvc\ModelInterface
63 | */
64 | static public function findFirstWith(...$arguments) {
65 | if (! empty ($arguments)) {
66 | $numArgs = count($arguments);
67 | $lastArg = $numArgs - 1;
68 | $parameters = NULL;
69 |
70 | if ($numArgs >= 2 && is_array($arguments[$lastArg])) {
71 | $parameters = $arguments[$lastArg];
72 |
73 | unset ($arguments[$lastArg]);
74 |
75 | if (isset ($parameters['columns'])) {
76 | throw new \LogicException('Results from database must be full models, do not use `columns` key');
77 | }
78 | }
79 | }
80 | else {
81 | throw new \BadMethodCallException(sprintf('%s requires at least one argument', __METHOD__));
82 | }
83 |
84 | if ($ret = static::findFirst($parameters)) {
85 | $ret = Loader::fromModel($ret, ...$arguments);
86 | }
87 |
88 | return $ret;
89 | }
90 |
91 | /**
92 | *
93 | * load('Robots.Parts');
98 | *
99 | * foreach ($manufacturer->robots as $robot) {
100 | * foreach ($robot->parts as $part) { ... }
101 | * }
102 | *
103 | *
104 | * @param mixed ...$arguments
105 | * @return self
106 | */
107 | public function load(...$arguments) {
108 | return Loader::fromModel($this, ...$arguments);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/tests/EagerLoadingTests.php:
--------------------------------------------------------------------------------
1 | robot;
10 |
11 | $eagerly = Loader::fromModel(Bug::findFirstById(1), 'Robot');
12 |
13 | $this->assertTrue(property_exists($eagerly, 'robot'));
14 | $this->assertInstanceOf('Robot', $eagerly->robot);
15 | $this->assertEquals($rawly->robot->readAttribute('id'), $eagerly->robot->readAttribute('id'));
16 |
17 | // Reverse
18 | $rawly = Robot::findFirstById(2);
19 | $rawly->bugs = $this->_resultSetToEagerLoadingEquivalent($rawly->bugs);
20 |
21 | $eagerly = Loader::fromModel(Robot::findFirstById(2), 'Bugs');
22 |
23 | $this->assertTrue(property_exists($eagerly, 'bugs'));
24 | $this->assertContainsOnlyInstancesOf('Bug', $eagerly->bugs);
25 |
26 | $getIds = function ($obj) {
27 | return $obj->readAttribute('id');
28 | };
29 |
30 | $this->assertEquals(array_map($getIds, $rawly->bugs), array_map($getIds, $eagerly->bugs));
31 | $this->assertEmpty(Loader::fromModel(Robot::findFirstById(1), 'Bugs')->bugs);
32 |
33 | // Test from multiple
34 | $rawly = $this->_resultSetToEagerLoadingEquivalent(Bug::find(['limit' => 10]));
35 | foreach ($rawly as $bug) {
36 | $bug->robot;
37 | }
38 |
39 | $eagerly = Loader::fromResultset(Bug::find(['limit' => 10]), 'Robot');
40 |
41 | $this->assertTrue(is_array($eagerly));
42 | $this->assertTrue(array_reduce($eagerly, function ($res, $bug) { return $res && property_exists($bug, 'robot'); }, TRUE));
43 |
44 | $getIds = function ($obj) {
45 | return property_exists($obj, 'robot') && isset ($obj->robot) ? $obj->robot->readAttribute('id') : NULL;
46 | };
47 |
48 | $this->assertEquals(array_map($getIds, $rawly), array_map($getIds, $eagerly));
49 | }
50 |
51 | public function testBelongsToDeep() {
52 | $rawly = Manufacturer::findFirstById(1);
53 | $rawly->robots = $this->_resultSetToEagerLoadingEquivalent($rawly->robots);
54 |
55 | foreach ($rawly->robots as $robot) {
56 | $robot->parent;
57 | }
58 |
59 | $eagerly = Loader::fromModel(Manufacturer::findFirstById(1), 'Robots.Parent');
60 |
61 | $this->assertTrue(property_exists($eagerly->robots[0], 'parent'));
62 | $this->assertNull($eagerly->robots[0]->parent);
63 | $this->assertInstanceOf('Robot', $eagerly->robots[2]->parent);
64 |
65 | $getIds = function ($obj) {
66 | return property_exists($obj, 'parent') && isset ($obj->parent) ? $obj->parent->readAttribute('id') : NULL;
67 | };
68 |
69 | $this->assertEquals(array_map($getIds, $eagerly->robots), array_map($getIds, $rawly->robots));
70 | }
71 |
72 | public function testHasOne() {
73 | $rawly = Robot::findFirstById(1);
74 | $rawly->purpose;
75 |
76 | $eagerly = Loader::fromModel(Robot::findFirstById(1), 'Purpose');
77 |
78 | $this->assertTrue(property_exists($eagerly, 'purpose'));
79 | $this->assertInstanceOf('Purpose', $eagerly->purpose);
80 | $this->assertEquals($rawly->purpose->readAttribute('id'), $eagerly->purpose->readAttribute('id'));
81 | }
82 |
83 | public function testHasMany() {
84 | $rawly = Manufacturer::findFirstById(1);
85 | $rawly->robots;
86 |
87 | $eagerly = Loader::fromModel(Manufacturer::findFirstById(1), 'Robots');
88 |
89 | $this->assertTrue(property_exists($eagerly, 'robots'));
90 | $this->assertTrue(is_array($eagerly->robots));
91 | $this->assertSame(count($eagerly->robots), $rawly->robots->count());
92 |
93 | $getIds = function ($arr) {
94 | $ret = [];
95 |
96 | foreach ($arr as $r) {
97 | if (is_object($r))
98 | $ret[] = $r->readAttribute('id');
99 | }
100 |
101 | return $ret;
102 | };
103 |
104 | $this->assertEquals(
105 | $getIds($this->_resultSetToEagerLoadingEquivalent($rawly->robots)),
106 | $getIds($eagerly->robots)
107 | );
108 | }
109 |
110 | public function testHasManyToMany() {
111 | $rawly = Robot::findFirstById(1);
112 | $rawly->parts;
113 |
114 | $eagerly = Loader::fromModel(Robot::findFirstById(1), 'Parts');
115 |
116 | $this->assertTrue(property_exists($eagerly, 'parts'));
117 | $this->assertTrue(is_array($eagerly->parts));
118 | $this->assertSame(count($eagerly->parts), $rawly->parts->count());
119 |
120 | $getIds = function ($arr) {
121 | $ret = [];
122 |
123 | foreach ($arr as $r) {
124 | if (is_object($r))
125 | $ret[] = $r->readAttribute('id');
126 | }
127 |
128 | return $ret;
129 | };
130 |
131 | $this->assertEquals(
132 | $getIds($this->_resultSetToEagerLoadingEquivalent($rawly->parts)),
133 | $getIds($eagerly->parts)
134 | );
135 | }
136 |
137 | public function testModelMethods() {
138 | $this->assertTrue(is_array(Robot::with('Parts')));
139 | $this->assertTrue(is_object(Robot::findFirstById(1)->load('Parts')));
140 | $this->assertTrue(is_object(Robot::findFirstWith('Parts', ['id = 1'])));
141 | }
142 |
143 | /**
144 | * @dataProvider dp1
145 | */
146 | public function testShouldThrowBadMethodCallExceptionIfArgumentsWereNotProvided($method) {
147 | $this->setExpectedException('BadMethodCallException');
148 | call_user_func(['Robot', $method]);
149 | }
150 |
151 | public function dp1() {
152 | return [['with'], ['findFirstWith']];
153 | }
154 |
155 | /**
156 | * @dataProvider dp2
157 | */
158 | public function testShouldThrowLogicExceptionIfTheEntityWillBeIncomplete($method, $args) {
159 | $this->setExpectedException('LogicException');
160 | call_user_func_array(['Robot', $method], $args);
161 | }
162 |
163 | public function dp2() {
164 | return [
165 | ['with', ['Parts', ['columns' => 'id']]],
166 | ['findFirstWith', ['Parts', ['columns' => 'id']]],
167 | ['with', [['Parts' => function ($builder) { $builder->columns(['id']); }]]],
168 | ];
169 | }
170 |
171 | /**
172 | * @dataProvider dp3
173 | */
174 | public function testShouldThrowInvalidArgumentExceptionIfLoaderSubjectIsNotValid($args) {
175 | $this->setExpectedException('InvalidArgumentException');
176 | (new ReflectionClass(Loader::class))->newInstance($args);
177 | }
178 |
179 | public function dp3() {
180 | return [
181 | [range(0, 5)],
182 | [[Robot::findFirstById(1), Bug::findFirstById(1)]]
183 | ];
184 | }
185 |
186 | /**
187 | * @dataProvider dp4
188 | */
189 | public function testShouldThrowRuntimeExceptionIfTheRelationIsNotDefinedOrSupported($args) {
190 | $this->setExpectedException('RuntimeException');
191 | (new ReflectionClass(Loader::class))->newInstanceArgs($args)->execute();
192 | }
193 |
194 | public function dp4() {
195 | return [
196 | [[Robot::findFirst(), 'NotSupportedRelations']],
197 | [[Robot::findFirst(), 'NonexistentRelation']],
198 | ];
199 | }
200 |
201 | public function testManyEagerLoadsAndConstraints() {
202 | $manufacturers = Manufacturer::with([
203 | 'Robots' => function ($builder) {
204 | $builder->where('id < 25');
205 | },
206 | 'Robots.Bugs' => function ($builder) {
207 | $builder->limit(2);
208 | },
209 | 'Robots.Parts'
210 | ], ['id < 50']);
211 |
212 | $this->assertEquals(
213 | array_sum(array_map(function ($o) { return count($o->robots); }, $manufacturers)),
214 | Robot::count(['id < 25 AND manufacturer_id < 50'])
215 | );
216 |
217 | $this->assertEquals(
218 | array_sum(array_map(function ($o) {
219 | $c = 0; foreach ($o->robots as $r) $c += count($r->bugs); return $c;
220 | }, $manufacturers)),
221 | 2
222 | );
223 |
224 | $manufacturers = Manufacturer::with([
225 | 'Robots.Bugs' => function ($builder) {
226 | $builder->where('id > 10000');
227 | }
228 | ], ['limit' => 5, 'order' => 'id ASC']);
229 |
230 | $this->assertEquals(
231 | array_sum(array_map(function ($o) { return count($o->robots); }, $manufacturers)),
232 | Robot::count(['manufacturer_id < 6'])
233 | );
234 |
235 | $robots = array ();
236 | foreach ($manufacturers as $m) $robots = array_merge($robots, $m->robots);
237 |
238 | $this->assertEquals(
239 | array_sum(array_map(function ($o) { return count($o->bugs); }, $robots)),
240 | 0
241 | );
242 | }
243 |
244 | public function testManyEagerLoadsAndConstraintsWithLoaderFromResultset() {
245 | $manufacturers = Loader::fromResultset(
246 | Manufacturer::find(),
247 | [
248 | 'Robots.Bugs' => function (QueryBuilder $builder) {
249 | $builder->where('Bug.id > 10');
250 | }
251 | ]
252 | );
253 |
254 | $robots = array ();
255 | foreach ($manufacturers as $m) $robots = array_merge($robots, $m->robots);
256 |
257 | $this->assertEquals(
258 | array_sum(array_map(function ($o) { return count($o->bugs); }, $robots)),
259 | 134
260 | );
261 | }
262 |
263 | public function testIssue4() {
264 | // Has many -> Belongs to
265 | // Should be the same for Has many -> Has one
266 | $this->assertEquals((new Loader(Robot::findFirstById(1), 'Bugs.Robot'))->execute()->get()->bugs, []);
267 | }
268 |
269 | protected function _resultSetToEagerLoadingEquivalent($val) {
270 | $ret = $val;
271 |
272 | if ($val instanceof Phalcon\Mvc\Model\Resultset\Simple) {
273 | $ret = [];
274 |
275 | if ($val->count() > 0) {
276 | foreach ($val as $model) {
277 | $ret[] = $model;
278 | }
279 | }
280 | }
281 |
282 | return $ret;
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/tests/Models/AbstractModel.php:
--------------------------------------------------------------------------------
1 | belongsTo('robot_id', 'Robot', 'id', [
12 | 'alias' => 'Robot',
13 | 'foreignKey' => [
14 | 'action' => Relation::ACTION_CASCADE
15 | ]
16 | ]);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Models/Manufacturer.php:
--------------------------------------------------------------------------------
1 | hasMany('id', 'Robot', 'manufacturer_id', [
11 | 'alias' => 'Robots',
12 | 'foreignKey' => [
13 | 'action' => Relation::ACTION_CASCADE
14 | ]
15 | ]);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Models/NotSupportedRelation.php:
--------------------------------------------------------------------------------
1 | belongsTo(['id','robot_id'], 'Robots', ['id','robot_id'], [
16 | 'alias' => 'Robot',
17 | 'foreignKey' => TRUE
18 | ]);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Models/Part.php:
--------------------------------------------------------------------------------
1 | hasManyToMany('id', 'RobotPart', 'part_id', 'robot_id', 'Robot', 'id', [
11 | 'alias' => 'Robots',
12 | 'foreignKey' => [
13 | 'action' => Relation::ACTION_RESTRICT
14 | ]
15 | ]);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Models/Purpose.php:
--------------------------------------------------------------------------------
1 | belongsTo('robot_id', 'Robot', 'id', [
12 | 'alias' => 'Robot',
13 | 'foreignKey' => [
14 | 'action' => Relation::ACTION_RESTRICT
15 | ]
16 | ]);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Models/Robot.php:
--------------------------------------------------------------------------------
1 | belongsTo('manufacturer_id', 'Manufacturer', 'id', [
13 | 'alias' => 'Manufacturer',
14 | 'foreignKey' => TRUE
15 | ]);
16 |
17 | // Recursive relation
18 | $this->belongsTo('parent_id', 'Robot', 'id', [
19 | 'alias' => 'Parent',
20 | 'foreignKey' => TRUE
21 | ]);
22 |
23 | $this->hasMany('id', 'Robot', 'parent_id', [
24 | 'alias' => 'Children',
25 | 'foreignKey' => [
26 | 'action' => Relation::ACTION_CASCADE
27 | ]
28 | ]);
29 |
30 | $this->hasOne('id', 'Purpose', 'robot_id', [
31 | 'alias' => 'Purpose',
32 | 'foreignKey' => [
33 | 'action' => Relation::ACTION_CASCADE
34 | ]
35 | ]);
36 |
37 | $this->hasMany('id', 'Bug', 'robot_id', [
38 | 'alias' => 'Bugs',
39 | 'foreignKey' => [
40 | 'action' => Relation::ACTION_CASCADE
41 | ]
42 | ]);
43 |
44 | $this->hasManyToMany('id', 'RobotPart', 'robot_id', 'part_id', 'Part', 'id', [
45 | 'alias' => 'Parts',
46 | 'foreignKey' => [
47 | 'action' => Relation::ACTION_CASCADE
48 | ]
49 | ]);
50 |
51 | // Wrong relation
52 | $this->hasMany(['id','parent_id'], 'NotSupportedRelation', ['id','robot_id'], [
53 | 'alias' => 'NotSupportedRelations',
54 | 'foreignKey' => [
55 | 'action' => Relation::ACTION_CASCADE
56 | ]
57 | ]);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Models/RobotPart.php:
--------------------------------------------------------------------------------
1 | belongsTo('robot_id', 'Robot', 'id', [
9 | 'foreignKey' => TRUE
10 | ]);
11 |
12 | $this->belongsTo('part_id', 'Part', 'id', [
13 | 'foreignKey' => TRUE
14 | ]);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | 'localhost',
5 | 'port' => '3306',
6 | 'username' => 'root',
7 | 'password' => '',
8 | 'dbname' => 'eager_loading_tests',
9 | 'charset' => 'utf8mb4',
10 | ];
11 |
12 | $di = new Phalcon\DI;
13 |
14 | $di->set('modelsMetadata', function () {
15 | return new Phalcon\Mvc\Model\Metadata\Memory;
16 | }, TRUE);
17 |
18 | $di->set('modelsManager', function () {
19 | return new Phalcon\Mvc\Model\Manager;
20 | }, TRUE);
21 |
22 | $di->set('db', function () {
23 | return new Phalcon\Db\Adapter\Pdo\Mysql(DB_CONFIG);
24 | }, TRUE);
25 |
26 | require_once __DIR__ . '/../src/EagerLoadingTrait.php';
27 | require_once __DIR__ . '/../src/EagerLoading/QueryBuilder.php';
28 | require_once __DIR__ . '/../src/EagerLoading/Loader.php';
29 | require_once __DIR__ . '/../src/EagerLoading/EagerLoad.php';
30 |
31 | spl_autoload_register(function ($class) {
32 | if (ctype_alpha($class)) {
33 | $file = __DIR__ . "/Models/{$class}.php";
34 |
35 | if (file_exists($file)) {
36 | require $file;
37 | }
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/tests/schema.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS `bug` (
2 | `id` serial,
3 | `name` varchar(100) NOT NULL,
4 | `robot_id` bigint unsigned NOT NULL,
5 | PRIMARY KEY (`id`),
6 | KEY (`robot_id`)
7 | );
8 |
9 | CREATE TABLE IF NOT EXISTS `manufacturer` (
10 | `id` serial,
11 | `name` varchar(100) NOT NULL,
12 | PRIMARY KEY (`id`)
13 | );
14 |
15 | CREATE TABLE IF NOT EXISTS `part` (
16 | `id` serial,
17 | `name` varchar(1020) NOT NULL,
18 | PRIMARY KEY (`id`)
19 | );
20 |
21 | CREATE TABLE IF NOT EXISTS `purpose` (
22 | `id` serial,
23 | `name` varchar(100) NOT NULL,
24 | `robot_id` bigint unsigned NOT NULL,
25 | PRIMARY KEY (`id`),
26 | KEY (`robot_id`)
27 | );
28 |
29 | CREATE TABLE IF NOT EXISTS `robot` (
30 | `id` serial,
31 | `name` varchar(100) NOT NULL,
32 | `parent_id` bigint unsigned DEFAULT NULL,
33 | `manufacturer_id` bigint unsigned NOT NULL,
34 | PRIMARY KEY (`id`),
35 | KEY (`parent_id`),
36 | KEY (`manufacturer_id`)
37 | );
38 |
39 | CREATE TABLE IF NOT EXISTS `robot_part` (
40 | `robot_id` bigint unsigned NOT NULL,
41 | `part_id` bigint unsigned NOT NULL,
42 | PRIMARY KEY (`robot_id`, `part_id`)
43 | );
44 |
45 | INSERT INTO `bug` (`id`,`name`,`robot_id`) VALUES (1,'a-0',10),(2,'b-0',80),(3,'c-0',18),(4,'d-0',127),(5,'e-0',53),(6,'f-0',94),(7,'g-0',134),(8,'h-0',156),(9,'i-0',127),(10,'j-0',45),(11,'k-0',41),(12,'l-0',51),(13,'m-0',68),(14,'n-0',4),(15,'o-0',144),(16,'p-0',23),(17,'q-0',34),(18,'r-0',146),(19,'s-0',55),(20,'t-0',39),(21,'u-0',66),(22,'v-0',33),(23,'w-0',44),(24,'x-0',74),(25,'y-0',89),(26,'z-0',46),(27,'a-1',67),(28,'b-1',150),(29,'c-1',29),(30,'d-1',50),(31,'e-1',133),(32,'f-1',151),(33,'g-1',122),(34,'h-1',158),(35,'i-1',158),(36,'j-1',167),(37,'k-1',194),(38,'l-1',169),(39,'m-1',81),(40,'n-1',18),(41,'o-1',143),(42,'p-1',5),(43,'q-1',110),(44,'r-1',48),(45,'s-1',13),(46,'t-1',79),(47,'u-1',34),(48,'v-1',47),(49,'w-1',23),(50,'x-1',175),(51,'y-1',74),(52,'z-1',27),(53,'a-2',114),(54,'b-2',139),(55,'c-2',200),(56,'d-2',109),(57,'e-2',3),(58,'f-2',19),(59,'g-2',29),(60,'h-2',116),(61,'i-2',22),(62,'j-2',29),(63,'k-2',63),(64,'l-2',70),(65,'m-2',51),(66,'n-2',103),(67,'o-2',12),(68,'p-2',60),(69,'q-2',104),(70,'r-2',54),(71,'s-2',149),(72,'t-2',2),(73,'u-2',96),(74,'v-2',53),(75,'w-2',93),(76,'x-2',158),(77,'y-2',175),(78,'z-2',167),(79,'a-3',39),(80,'b-3',33),(81,'c-3',64),(82,'d-3',167),(83,'e-3',14),(84,'f-3',73),(85,'g-3',190),(86,'h-3',45),(87,'i-3',131),(88,'j-3',89),(89,'k-3',143),(90,'l-3',198),(91,'m-3',158),(92,'n-3',191),(93,'o-3',21),(94,'p-3',197),(95,'q-3',84),(96,'r-3',77),(97,'s-3',42),(98,'t-3',102),(99,'u-3',34),(100,'v-3',134),(101,'w-3',46),(102,'x-3',39),(103,'y-3',129),(104,'z-3',174),(105,'a-4',136),(106,'b-4',79),(107,'c-4',190),(108,'d-4',177),(109,'e-4',29),(110,'f-4',199),(111,'g-4',91),(112,'h-4',108),(113,'i-4',76),(114,'j-4',129),(115,'k-4',28),(116,'l-4',195),(117,'m-4',57),(118,'n-4',59),(119,'o-4',25),(120,'p-4',85),(121,'q-4',99),(122,'r-4',31),(123,'s-4',75),(124,'t-4',43),(125,'u-4',188),(126,'v-4',77),(127,'w-4',10),(128,'x-4',155),(129,'y-4',198),(130,'z-4',41),(131,'a-5',72),(132,'b-5',174),(133,'c-5',144),(134,'d-5',52),(135,'e-5',80),(136,'f-5',162),(137,'g-5',20),(138,'h-5',47),(139,'i-5',2),(140,'j-5',22),(141,'k-5',57),(142,'l-5',9),(143,'m-5',107),(144,'n-5',99);
46 |
47 | INSERT INTO `manufacturer` (`id`,`name`) VALUES (1,'a-0'),(2,'b-0'),(3,'c-0'),(4,'d-0'),(5,'e-0'),(6,'f-0'),(7,'g-0'),(8,'h-0'),(9,'i-0'),(10,'j-0'),(11,'k-0'),(12,'l-0'),(13,'m-0'),(14,'n-0'),(15,'o-0'),(16,'p-0'),(17,'q-0'),(18,'r-0'),(19,'s-0'),(20,'t-0'),(21,'u-0'),(22,'v-0'),(23,'w-0'),(24,'x-0'),(25,'y-0'),(26,'z-0'),(27,'a-1'),(28,'b-1'),(29,'c-1'),(30,'d-1'),(31,'e-1'),(32,'f-1'),(33,'g-1'),(34,'h-1'),(35,'i-1'),(36,'j-1'),(37,'k-1'),(38,'l-1'),(39,'m-1'),(40,'n-1'),(41,'o-1'),(42,'p-1'),(43,'q-1'),(44,'r-1'),(45,'s-1'),(46,'t-1'),(47,'u-1'),(48,'v-1'),(49,'w-1'),(50,'x-1'),(51,'y-1'),(52,'z-1'),(53,'a-2'),(54,'b-2'),(55,'c-2'),(56,'d-2'),(57,'e-2'),(58,'f-2'),(59,'g-2'),(60,'h-2'),(61,'i-2'),(62,'j-2'),(63,'k-2'),(64,'l-2'),(65,'m-2'),(66,'n-2'),(67,'o-2'),(68,'p-2'),(69,'q-2'),(70,'r-2'),(71,'s-2'),(72,'t-2'),(73,'u-2'),(74,'v-2'),(75,'w-2'),(76,'x-2'),(77,'y-2'),(78,'z-2'),(79,'a-3'),(80,'b-3'),(81,'c-3'),(82,'d-3'),(83,'e-3'),(84,'f-3'),(85,'g-3'),(86,'h-3'),(87,'i-3'),(88,'j-3'),(89,'k-3'),(90,'l-3'),(91,'m-3'),(92,'n-3'),(93,'o-3'),(94,'p-3'),(95,'q-3'),(96,'r-3'),(97,'s-3'),(98,'t-3'),(99,'u-3'),(100,'v-3');
48 |
49 | INSERT INTO `part` (`id`,`name`) VALUES (1,'a-0'),(2,'b-0'),(3,'c-0'),(4,'d-0'),(5,'e-0'),(6,'f-0'),(7,'g-0'),(8,'h-0'),(9,'i-0'),(10,'j-0'),(11,'k-0'),(12,'l-0'),(13,'m-0'),(14,'n-0'),(15,'o-0'),(16,'p-0'),(17,'q-0'),(18,'r-0'),(19,'s-0'),(20,'t-0'),(21,'u-0'),(22,'v-0'),(23,'w-0'),(24,'x-0'),(25,'y-0'),(26,'z-0'),(27,'a-1'),(28,'b-1'),(29,'c-1'),(30,'d-1'),(31,'e-1'),(32,'f-1'),(33,'g-1'),(34,'h-1'),(35,'i-1'),(36,'j-1'),(37,'k-1'),(38,'l-1'),(39,'m-1'),(40,'n-1'),(41,'o-1'),(42,'p-1'),(43,'q-1'),(44,'r-1'),(45,'s-1'),(46,'t-1'),(47,'u-1'),(48,'v-1'),(49,'w-1'),(50,'x-1'),(51,'y-1'),(52,'z-1'),(53,'a-2'),(54,'b-2'),(55,'c-2'),(56,'d-2'),(57,'e-2'),(58,'f-2'),(59,'g-2'),(60,'h-2'),(61,'i-2'),(62,'j-2'),(63,'k-2'),(64,'l-2'),(65,'m-2'),(66,'n-2'),(67,'o-2'),(68,'p-2'),(69,'q-2'),(70,'r-2'),(71,'s-2'),(72,'t-2'),(73,'u-2'),(74,'v-2'),(75,'w-2'),(76,'x-2'),(77,'y-2'),(78,'z-2'),(79,'a-3'),(80,'b-3'),(81,'c-3'),(82,'d-3'),(83,'e-3'),(84,'f-3'),(85,'g-3'),(86,'h-3'),(87,'i-3'),(88,'j-3'),(89,'k-3'),(90,'l-3'),(91,'m-3'),(92,'n-3'),(93,'o-3'),(94,'p-3'),(95,'q-3'),(96,'r-3'),(97,'s-3'),(98,'t-3'),(99,'u-3'),(100,'v-3');
50 |
51 | INSERT INTO `purpose` (`id`,`name`,`robot_id`) VALUES (1,'a-0',1),(2,'b-0',2),(3,'c-0',3),(4,'d-0',4),(5,'e-0',5),(6,'f-0',6),(7,'g-0',7),(8,'h-0',8),(9,'i-0',9),(10,'j-0',10),(11,'k-0',11),(12,'l-0',12),(13,'m-0',13),(14,'n-0',14),(15,'o-0',15),(16,'p-0',16),(17,'q-0',17),(18,'r-0',18),(19,'s-0',19),(20,'t-0',20),(21,'u-0',21),(22,'v-0',22),(23,'w-0',23),(24,'x-0',24),(25,'y-0',25),(26,'z-0',26),(27,'a-1',27),(28,'b-1',28),(29,'c-1',29),(30,'d-1',30),(31,'e-1',31),(32,'f-1',32),(33,'g-1',33),(34,'h-1',34),(35,'i-1',35),(36,'j-1',36),(37,'k-1',37),(38,'l-1',38),(39,'m-1',39),(40,'n-1',40),(41,'o-1',41),(42,'p-1',42),(43,'q-1',43),(44,'r-1',44),(45,'s-1',45),(46,'t-1',46),(47,'u-1',47),(48,'v-1',48),(49,'w-1',49),(50,'x-1',50),(51,'y-1',51),(52,'z-1',52),(53,'a-2',53),(54,'b-2',54),(55,'c-2',55),(56,'d-2',56),(57,'e-2',57),(58,'f-2',58),(59,'g-2',59),(60,'h-2',60),(61,'i-2',61),(62,'j-2',62),(63,'k-2',63),(64,'l-2',64),(65,'m-2',65),(66,'n-2',66),(67,'o-2',67),(68,'p-2',68),(69,'q-2',69),(70,'r-2',70),(71,'s-2',71),(72,'t-2',72),(73,'u-2',73),(74,'v-2',74),(75,'w-2',75),(76,'x-2',76),(77,'y-2',77),(78,'z-2',78),(79,'a-3',79),(80,'b-3',80),(81,'c-3',81),(82,'d-3',82),(83,'e-3',83),(84,'f-3',84),(85,'g-3',85),(86,'h-3',86),(87,'i-3',87),(88,'j-3',88),(89,'k-3',89),(90,'l-3',90),(91,'m-3',91),(92,'n-3',92),(93,'o-3',93),(94,'p-3',94),(95,'q-3',95),(96,'r-3',96),(97,'s-3',97),(98,'t-3',98),(99,'u-3',99),(100,'v-3',100),(101,'w-3',101),(102,'x-3',102),(103,'y-3',103),(104,'z-3',104),(105,'a-4',105),(106,'b-4',106),(107,'c-4',107),(108,'d-4',108),(109,'e-4',109),(110,'f-4',110),(111,'g-4',111),(112,'h-4',112),(113,'i-4',113),(114,'j-4',114),(115,'k-4',115),(116,'l-4',116),(117,'m-4',117),(118,'n-4',118),(119,'o-4',119),(120,'p-4',120),(121,'q-4',121),(122,'r-4',122),(123,'s-4',123),(124,'t-4',124),(125,'u-4',125),(126,'v-4',126),(127,'w-4',127),(128,'x-4',128),(129,'y-4',129),(130,'z-4',130),(131,'a-5',131),(132,'b-5',132),(133,'c-5',133),(134,'d-5',134),(135,'e-5',135),(136,'f-5',136),(137,'g-5',137),(138,'h-5',138),(139,'i-5',139),(140,'j-5',140),(141,'k-5',141),(142,'l-5',142),(143,'m-5',143),(144,'n-5',144),(145,'o-5',145),(146,'p-5',146),(147,'q-5',147),(148,'r-5',148),(149,'s-5',149),(150,'t-5',150),(151,'u-5',151),(152,'v-5',152),(153,'w-5',153),(154,'x-5',154),(155,'y-5',155),(156,'z-5',156),(157,'a-6',157),(158,'b-6',158),(159,'c-6',159),(160,'d-6',160),(161,'e-6',161),(162,'f-6',162),(163,'g-6',163),(164,'h-6',164),(165,'i-6',165),(166,'j-6',166),(167,'k-6',167),(168,'l-6',168),(169,'m-6',169),(170,'n-6',170),(171,'o-6',171),(172,'p-6',172),(173,'q-6',173),(174,'r-6',174),(175,'s-6',175),(176,'t-6',176),(177,'u-6',177),(178,'v-6',178),(179,'w-6',179),(180,'x-6',180),(181,'y-6',181),(182,'z-6',182),(183,'a-7',183),(184,'b-7',184),(185,'c-7',185),(186,'d-7',186),(187,'e-7',187),(188,'f-7',188),(189,'g-7',189),(190,'h-7',190),(191,'i-7',191),(192,'j-7',192),(193,'k-7',193),(194,'l-7',194),(195,'m-7',195),(196,'n-7',196),(197,'o-7',197),(198,'p-7',198),(199,'q-7',199),(200,'r-7',200);
52 |
53 | INSERT INTO `robot` (`id`,`name`,`parent_id`,`manufacturer_id`) VALUES (1,'a-0',NULL,85),(2,'b-0',1,92),(3,'c-0',1,64),(4,'d-0',NULL,16),(5,'e-0',NULL,43),(6,'f-0',NULL,2),(7,'g-0',6,48),(8,'h-0',NULL,83),(9,'i-0',8,81),(10,'j-0',1,75),(11,'k-0',3,61),(12,'l-0',NULL,61),(13,'m-0',6,78),(14,'n-0',4,100),(15,'o-0',4,71),(16,'p-0',NULL,47),(17,'q-0',7,96),(18,'r-0',9,87),(19,'s-0',16,17),(20,'t-0',NULL,99),(21,'u-0',9,21),(22,'v-0',10,10),(23,'w-0',15,77),(24,'x-0',1,87),(25,'y-0',10,90),(26,'z-0',NULL,100),(27,'a-1',13,79),(28,'b-1',NULL,70),(29,'c-1',6,4),(30,'d-1',4,32),(31,'e-1',16,30),(32,'f-1',NULL,58),(33,'g-1',27,75),(34,'h-1',19,94),(35,'i-1',NULL,94),(36,'j-1',NULL,1),(37,'k-1',NULL,96),(38,'l-1',NULL,8),(39,'m-1',2,56),(40,'n-1',34,4),(41,'o-1',32,25),(42,'p-1',21,94),(43,'q-1',NULL,94),(44,'r-1',NULL,69),(45,'s-1',NULL,30),(46,'t-1',16,47),(47,'u-1',1,72),(48,'v-1',NULL,79),(49,'w-1',NULL,84),(50,'x-1',NULL,30),(51,'y-1',NULL,37),(52,'z-1',7,69),(53,'a-2',50,9),(54,'b-2',NULL,44),(55,'c-2',NULL,71),(56,'d-2',NULL,6),(57,'e-2',NULL,50),(58,'f-2',55,97),(59,'g-2',NULL,66),(60,'h-2',5,2),(61,'i-2',35,2),(62,'j-2',18,4),(63,'k-2',8,13),(64,'l-2',36,65),(65,'m-2',NULL,10),(66,'n-2',58,99),(67,'o-2',NULL,23),(68,'p-2',5,94),(69,'q-2',NULL,68),(70,'r-2',16,54),(71,'s-2',15,71),(72,'t-2',16,5),(73,'u-2',NULL,14),(74,'v-2',NULL,29),(75,'w-2',53,69),(76,'x-2',36,84),(77,'y-2',NULL,13),(78,'z-2',36,33),(79,'a-3',9,91),(80,'b-3',42,62),(81,'c-3',NULL,68),(82,'d-3',NULL,47),(83,'e-3',27,68),(84,'f-3',27,22),(85,'g-3',68,51),(86,'h-3',NULL,64),(87,'i-3',3,23),(88,'j-3',71,28),(89,'k-3',74,2),(90,'l-3',NULL,17),(91,'m-3',63,5),(92,'n-3',NULL,48),(93,'o-3',2,84),(94,'p-3',92,24),(95,'q-3',88,5),(96,'r-3',NULL,98),(97,'s-3',NULL,44),(98,'t-3',8,71),(99,'u-3',NULL,25),(100,'v-3',66,90),(101,'w-3',NULL,2),(102,'x-3',67,57),(103,'y-3',71,7),(104,'z-3',NULL,98),(105,'a-4',NULL,60),(106,'b-4',74,82),(107,'c-4',86,33),(108,'d-4',65,31),(109,'e-4',NULL,15),(110,'f-4',36,81),(111,'g-4',NULL,90),(112,'h-4',NULL,3),(113,'i-4',29,2),(114,'j-4',71,62),(115,'k-4',97,41),(116,'l-4',97,20),(117,'m-4',114,89),(118,'n-4',95,57),(119,'o-4',NULL,29),(120,'p-4',NULL,70),(121,'q-4',63,46),(122,'r-4',48,48),(123,'s-4',NULL,1),(124,'t-4',26,57),(125,'u-4',NULL,65),(126,'v-4',NULL,64),(127,'w-4',11,48),(128,'x-4',31,26),(129,'y-4',NULL,26),(130,'z-4',NULL,21),(131,'a-5',NULL,41),(132,'b-5',82,40),(133,'c-5',NULL,77),(134,'d-5',NULL,87),(135,'e-5',55,58),(136,'f-5',NULL,3),(137,'g-5',NULL,73),(138,'h-5',123,91),(139,'i-5',136,18),(140,'j-5',47,86),(141,'k-5',50,25),(142,'l-5',113,11),(143,'m-5',NULL,39),(144,'n-5',102,31),(145,'o-5',73,82),(146,'p-5',NULL,47),(147,'q-5',97,12),(148,'r-5',25,20),(149,'s-5',103,23),(150,'t-5',NULL,99),(151,'u-5',32,9),(152,'v-5',NULL,94),(153,'w-5',124,78),(154,'x-5',96,37),(155,'y-5',NULL,57),(156,'z-5',64,2),(157,'a-6',NULL,89),(158,'b-6',NULL,56),(159,'c-6',NULL,38),(160,'d-6',48,80),(161,'e-6',13,31),(162,'f-6',46,81),(163,'g-6',NULL,9),(164,'h-6',40,91),(165,'i-6',124,57),(166,'j-6',NULL,94),(167,'k-6',NULL,38),(168,'l-6',57,74),(169,'m-6',112,98),(170,'n-6',88,1),(171,'o-6',NULL,62),(172,'p-6',NULL,80),(173,'q-6',50,25),(174,'r-6',NULL,29),(175,'s-6',146,37),(176,'t-6',NULL,8),(177,'u-6',65,33),(178,'v-6',NULL,56),(179,'w-6',44,56),(180,'x-6',130,100),(181,'y-6',NULL,13),(182,'z-6',NULL,30),(183,'a-7',NULL,9),(184,'b-7',82,64),(185,'c-7',NULL,93),(186,'d-7',NULL,25),(187,'e-7',103,90),(188,'f-7',147,70),(189,'g-7',182,6),(190,'h-7',88,61),(191,'i-7',NULL,87),(192,'j-7',NULL,21),(193,'k-7',23,28),(194,'l-7',NULL,6),(195,'m-7',NULL,59),(196,'n-7',97,84),(197,'o-7',NULL,31),(198,'p-7',61,21),(199,'q-7',35,87),(200,'r-7',NULL,39);
54 |
55 | INSERT INTO `robot_part` (`robot_id`,`part_id`) VALUES (1,14),(1,38),(1,83),(2,57),(2,73),(2,76),(3,41),(3,52),(4,54),(4,55),(4,73),(5,98),(6,47),(7,54),(7,66),(8,34),(8,62),(8,72),(9,18),(9,86),(9,89),(10,66),(10,82),(10,86),(11,31),(11,74),(12,3),(12,86),(12,95),(13,58),(14,16),(14,31),(14,64),(15,76),(16,1),(16,59),(16,73),(17,13),(17,87),(18,28),(19,47),(19,61),(20,16),(20,69),(21,26),(21,74),(21,90),(22,20),(22,27),(23,27),(23,85),(24,50),(25,15),(25,49),(25,77),(26,38),(26,52),(26,75),(27,10),(27,100),(28,64),(28,93),(29,7),(29,53),(30,76),(31,49),(32,15),(33,83),(34,77),(35,30),(35,99),(36,93),(37,68),(37,94),(38,29),(39,72),(39,85),(40,14),(40,54),(40,93),(41,79),(42,91),(42,95),(42,98),(43,57),(43,73),(43,93),(44,33),(45,48),(46,52),(46,58),(47,21),(47,38),(48,16),(48,60),(49,79),(49,95),(50,25),(50,47),(50,54),(51,3),(52,37),(52,87),(53,37),(53,69),(54,28),(54,44),(54,82),(55,25),(55,61),(56,78),(57,63),(58,8),(58,96),(58,100),(59,46),(59,52),(60,25),(61,34),(61,53),(61,82),(62,88),(63,6),(63,83),(63,92),(64,32),(64,54),(65,43),(65,91),(66,16),(66,29),(66,90),(67,36),(67,78),(67,97),(68,22),(69,78),(70,24),(71,26),(72,75),(73,25),(73,61),(74,67),(75,31),(75,49),(75,66),(76,22),(76,24),(76,43),(77,22),(77,69),(77,94),(78,50),(79,45),(80,73),(80,76),(81,4),(82,79),(83,65),(83,69),(83,80),(84,6),(84,69),(85,4),(85,64),(85,96),(86,3),(87,15),(87,53),(88,33),(88,60),(89,37),(89,40),(89,54),(90,19),(90,62),(91,64),(91,88),(92,28),(92,52),(92,93),(93,43),(93,80),(94,74),(95,38),(95,61),(95,70),(96,41),(96,69),(97,4),(97,100),(98,29),(98,39),(99,5),(99,28),(99,93),(100,66),(101,45),(101,91),(102,2),(102,61),(102,95),(103,53),(104,64),(105,8),(105,43),(105,99),(106,18),(106,87),(106,90),(107,49),(107,55),(107,75),(108,8),(108,48),(108,73),(109,13),(109,89),(109,91),(110,16),(110,78),(111,63),(111,89),(112,75),(112,83),(113,60),(113,95),(114,11),(115,15),(115,36),(115,70),(116,6),(116,89),(117,17),(117,46),(118,15),(118,28),(119,17),(119,92),(120,67),(121,11),(121,90),(122,7),(122,94),(122,100),(123,79),(124,6),(124,26),(124,65),(125,91),(126,59),(127,83),(127,96),(127,97),(128,2),(129,69),(129,84),(130,7),(130,52),(131,12),(131,16),(132,1),(132,14),(133,1),(133,44),(133,75),(134,14),(134,17),(134,42),(135,9),(135,11),(135,16),(136,25),(136,40),(137,22),(137,86),(138,32),(139,35),(140,14),(140,50),(141,67),(141,78),(141,86),(142,49),(143,35),(143,44),(143,91),(144,13),(144,50),(145,25),(145,56),(145,64),(146,21),(146,46),(146,77),(147,14),(147,50),(148,46),(148,87),(149,90),(150,85),(151,8),(151,54),(152,69),(153,54),(153,56),(153,99),(154,38),(154,84),(155,44),(155,85),(156,18),(157,84),(158,22),(159,45),(160,33),(160,48),(161,36),(161,41),(161,80),(162,41),(162,85),(163,4),(163,32),(164,10),(164,39),(164,92),(165,55),(165,67),(166,58),(167,14),(167,24),(167,53),(168,10),(168,58),(168,71),(169,53),(169,68),(170,93),(171,18),(171,23),(171,74),(172,14),(172,32),(173,16),(174,28),(174,55),(175,61),(176,50),(177,19),(178,23),(178,56),(178,92),(179,21),(179,65),(180,42),(180,63),(181,93),(182,38),(182,69),(183,7),(184,1),(184,44),(184,89),(185,31),(185,100),(186,51),(187,32),(187,48),(187,70),(188,50),(188,57),(189,46),(190,80),(190,81),(191,45),(192,25),(193,26),(193,32),(194,33),(195,12),(195,84),(196,60),(197,73),(198,53),(199,14),(199,78),(200,18),(200,20),(200,83);
--------------------------------------------------------------------------------