├── 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); --------------------------------------------------------------------------------