1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
query.php
См. документацию.
1<?php
2
3namespace Bitrix\Main\ORM\Query;
4
5use Bitrix\Main;
6use Bitrix\Main\ORM\Entity;
7use Bitrix\Main\ORM\Fields\ArrayField;
8use Bitrix\Main\ORM\Fields\BooleanField;
9use Bitrix\Main\ORM\Fields\ExpressionField;
10use Bitrix\Main\ORM\Fields\Field;
11use Bitrix\Main\ORM\Fields\Relations\ManyToMany;
12use Bitrix\Main\ORM\Fields\Relations\OneToMany;
13use Bitrix\Main\ORM\Fields\Relations\Reference;
14use Bitrix\Main\ORM\Fields\ScalarField;
15use Bitrix\Main\ORM\Fields\TextField;
16use Bitrix\Main\ORM\Query\Filter\ConditionTree as Filter;
17use Bitrix\Main\ORM\Query\Filter\Expressions\ColumnExpression;
18use Bitrix\Main\SystemException;
19use Bitrix\Main\Text\StringHelper;
20
117class Query
118{
120 protected $entity;
121
122 protected
126 $limit = null,
127 $offset = null,
129
130 // deprecated array filter format
131 protected
135
137 protected $filterHandler;
138
140 protected $whereHandler;
141
143 protected $havingHandler;
144
148 protected // all chain storages keying by alias
152
156 protected
160
164 protected
165 $select_expr_chains = array(), // from select expr "build_from"
166 $having_expr_chains = array(), // from having expr "build_from"
167 $hidden_chains = array(); // all expr "build_from" elements;
168
174
177
179 protected $global_chains = array(); // keying by both def and alias
180
183
185 protected static $expressionHelper;
186
192 protected $data_doubling_off = false;
193
199 protected $private_fields_on = false;
200
202 protected $table_alias_postfix = '';
203
205 protected $custom_base_table_alias = null;
206
208 protected $join_map = array();
209
211 protected $join_registry;
212
214 protected $unionHandler;
215
217 protected $is_distinct = false;
218
220 protected $is_executing = false;
221
223 protected static $last_query;
224
226 protected $replaced_aliases = [];
227
229 protected $replaced_taliases = [];
230
232 protected $uniqueAliasCounter = 0;
233
236
237 protected
239 $cacheJoins = false;
240
247 public function __construct($source)
248 {
249 if ($source instanceof $this)
250 {
251 $this->entity = Entity::getInstanceByQuery($source);
252 }
253 elseif ($source instanceof Entity)
254 {
255 $this->entity = clone $source;
256 }
257 elseif (is_string($source))
258 {
259 $this->entity = clone Entity::getInstance($source);
260 }
261 else
262 {
263 throw new Main\ArgumentException(sprintf(
264 'Unknown source type "%s" for new %s', gettype($source), __CLASS__
265 ));
266 }
267
268 $this->filterHandler = static::filter();
269 $this->whereHandler = static::filter();
270 $this->havingHandler = static::filter();
271 }
272
280 public function __call($method, $arguments)
281 {
282 // where and having proxies
283 if (str_starts_with($method, 'having'))
284 {
285 $method = str_replace('having', 'where', $method);
286 }
287
288 if (str_starts_with($method, 'where'))
289 {
290 if (method_exists($this->filterHandler, $method))
291 {
292 call_user_func_array(
293 [$this->filterHandler, $method],
294 $arguments
295 );
296
297 return $this;
298 }
299 }
300
301 if (str_starts_with($method, 'with'))
302 {
303 $dataClass = $this->entity->getDataClass();
304
305 if (method_exists($dataClass, $method))
306 {
307 // set query as first element
308 array_unshift($arguments, $this);
309
310 call_user_func_array(
311 [$dataClass, $method],
312 $arguments
313 );
314
315 return $this;
316 }
317 }
318
319 throw new Main\SystemException("Unknown method `{$method}`");
320 }
321
327 public function getSelect()
328 {
329 return $this->select;
330 }
331
338 public function setSelect(array $select)
339 {
340 $this->select = $select;
341 return $this;
342 }
343
351 public function addSelect($definition, $alias = '')
352 {
353 if($alias <> '')
354 {
355 $this->select[$alias] = $definition;
356 }
357 else
358 {
359 $this->select[] = $definition;
360 }
361
362 return $this;
363 }
364
370 public function getFilter()
371 {
372 return $this->filter;
373 }
374
381 public function setFilter(array $filter)
382 {
383 $this->filter = $filter;
384 return $this;
385 }
386
394 public function addFilter($key, $value)
395 {
396 if (is_null($key) && is_array($value))
397 {
398 $this->filter[] = $value;
399 }
400 else
401 {
402 $this->filter[$key] = $value;
403 }
404
405 return $this;
406 }
407
411 public function getFilterHandler()
412 {
413 return $this->filterHandler;
414 }
415
421 public function getGroup()
422 {
423 return $this->group;
424 }
425
432 public function setGroup($group)
433 {
434 $group = !is_array($group) ? array($group) : $group;
435 $this->group = $group;
436
437 return $this;
438 }
439
446 public function addGroup($group)
447 {
448 $this->group[] = $group;
449 return $this;
450 }
451
457 public function getOrder()
458 {
459 return $this->order;
460 }
461
474 public function setOrder($order)
475 {
476 $this->order = array();
477
478 if (!is_array($order))
479 {
481 }
482
483 foreach ($order as $k => $v)
484 {
485 if (is_numeric($k))
486 {
487 $this->addOrder($v);
488 }
489 else
490 {
491 $this->addOrder($k, $v);
492 }
493 }
494
495 return $this;
496 }
497
508 public function addOrder($definition, $order = 'ASC')
509 {
510 if (!is_string($order))
511 {
512 throw new Main\ArgumentException('Order must be a string');
513 }
514
515 $order = strtoupper($order);
516
517 if (!in_array($order, array('ASC', 'DESC'), true))
518 {
519 throw new Main\ArgumentException(sprintf('Invalid order "%s"', $order));
520 }
521
522 $connection = $this->entity->getConnection();
523 $helper = $connection->getSqlHelper();
524
525 if ($order == 'ASC')
526 {
527 $order = $helper->getAscendingOrder();
528 }
529 else
530 {
531 $order = $helper->getDescendingOrder();
532 }
533
534 $this->order[$definition] = $order;
535
536 return $this;
537 }
538
544 public function getLimit()
545 {
546 return $this->limit;
547 }
548
555 public function setLimit($limit)
556 {
557 $this->limit = $limit;
558 return $this;
559 }
560
566 public function getOffset()
567 {
568 return $this->offset;
569 }
570
577 public function setOffset($offset)
578 {
579 $this->offset = $offset;
580 return $this;
581 }
582
583 public function countTotal($count = null)
584 {
585 if ($count === null)
586 {
587 return $this->countTotal;
588 }
589 else
590 {
591 $this->countTotal = (bool) $count;
592 return $this;
593 }
594 }
595
604 public function union()
605 {
606 foreach (func_get_args() as $arg)
607 {
608 $this->getUnionHandler()->addQuery(new UnionCondition($arg, false));
609 }
610
611 return $this;
612 }
613
622 public function unionAll()
623 {
624 foreach (func_get_args() as $arg)
625 {
626 $this->getUnionHandler()->addQuery(new UnionCondition($arg, true));
627 }
628
629 return $this;
630 }
631
642 public function setUnionOrder($order)
643 {
644 $this->getUnionHandler()->setOrder($order);
645 return $this;
646 }
647
660 public function addUnionOrder($definition, $order = 'ASC')
661 {
662 $this->getUnionHandler()->addOrder($definition, $order);
663 return $this;
664 }
665
674 public function setUnionLimit($limit)
675 {
676 $this->getUnionHandler()->setLimit($limit);
677 return $this;
678 }
679
688 public function setUnionOffset($offset)
689 {
690 $this->getUnionHandler()->setOffset($offset);
691 return $this;
692 }
693
699 public function enableDataDoubling()
700 {
701 $this->data_doubling_off = false;
702
703 return $this;
704 }
705
712 public function disableDataDoubling()
713 {
714 if (count($this->entity->getPrimaryArray()) !== 1)
715 {
716 // mssql doesn't support constructions WHERE (col1, col2) IN (SELECT col1, col2 FROM SomeOtherTable)
717 /* @see http://connect.microsoft.com/SQLServer/feedback/details/299231/add-support-for-ansi-standard-row-value-constructors */
718 trigger_error(sprintf(
719 'Disabling data doubling available for Entities with 1 primary field only. Number of primaries of your entity `%s` is %d.',
720 $this->entity->getFullName(), count($this->entity->getPrimaryArray())
721 ), E_USER_WARNING);
722 }
723 else
724 {
725 $this->data_doubling_off = true;
726 }
727
728 return $this;
729 }
730
736 public function enablePrivateFields()
737 {
738 $this->private_fields_on = true;
739
740 return $this;
741 }
742
748 public function disablePrivateFields()
749 {
750 $this->private_fields_on = false;
751
752 return $this;
753 }
754
758 public function isPrivateFieldsEnabled()
759 {
760 return $this->private_fields_on;
761 }
762
763 protected function checkForPrivateFields()
764 {
765 // check in filter
766 foreach ($this->filter_chains as $chain)
767 {
768 if (static::isFieldPrivate($chain->getLastElement()->getValue()))
769 {
770 $columnField = $chain->getLastElement()->getValue();
771
772 throw new SystemException(sprintf(
773 'Private field %s.%s is restricted in filter',
774 $columnField->getEntity()->getDataClass(),
775 $columnField->getName()
776 ));
777 }
778 }
779
780 // check in general
781 if ($this->private_fields_on !== true)
782 {
783 foreach ($this->global_chains as $chain)
784 {
785 if (static::isFieldPrivate($chain->getLastElement()->getValue()))
786 {
787 $columnField = $chain->getLastElement()->getValue();
788
789 throw new SystemException(sprintf(
790 'Private field %s.%s is restricted in query, use Query::enablePrivateFields() to allow it',
791 $columnField->getEntity()->getDataClass(),
792 $columnField->getName()
793 ));
794 }
795 }
796 }
797 }
798
806 public static function isFieldPrivate($field)
807 {
808 if ($field instanceof ScalarField)
809 {
810 return $field->isPrivate();
811 }
812 elseif ($field instanceof ExpressionField)
813 {
814 foreach ($field->getBuildFromChains() as $chain)
815 {
816 if (static::isFieldPrivate($chain->getLastElement()->getValue()))
817 {
818 return true;
819 }
820 }
821 }
822
823 return false;
824 }
825
836 public function registerRuntimeField($name, $fieldInfo = null)
837 {
838 if ($name instanceof Field && $fieldInfo === null)
839 {
840 // short call for Field objects
841 $fieldInfo = $name;
842 $name = $fieldInfo->getName();
843 }
844 elseif ((empty($name) || is_numeric($name)) && $fieldInfo instanceof Field)
845 {
846 $name = $fieldInfo->getName();
847 }
848
849 // clone field as long as Field object could be initialized only once
850 // there is no need to initialize original object
851 if ($fieldInfo instanceof Field)
852 {
853 $fieldInfo = clone $fieldInfo;
854 }
855
856 // attach field to the entity
857 $this->entity->addField($fieldInfo, $name);
858
859 // force chain creation for further needs
860 $chain = $this->getRegisteredChain($name, true);
861 $this->registerChain('runtime', $chain);
862
863 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
864 {
865 $this->collectExprChains($chain, array('hidden'));
866 }
867
868 return $this;
869 }
870
871 public function setTableAliasPostfix($postfix)
872 {
873 $this->table_alias_postfix = $postfix;
874 return $this;
875 }
876
877 public function getTableAliasPostfix()
878 {
879 return $this->table_alias_postfix;
880 }
881
889 public function setCustomBaseTableAlias($alias)
890 {
891 $this->custom_base_table_alias = $alias;
892 return $this;
893 }
894
906 public static function filter()
907 {
908 return new Filter;
909 }
910
919 public static function expr($alias = null)
920 {
921 if (static::$expressionHelper === null)
922 {
923 static::$expressionHelper = new Expression;
924 }
925
926 static::$expressionHelper->alias = $alias;
927
928 return static::$expressionHelper;
929 }
930
938 public function exec()
939 {
940 $this->is_executing = true;
941
942 $query = $this->buildQuery();
943
944 $cacheId = "";
945 $ttl = 0;
946 $result = null;
947
948 if($this->cacheTtl > 0 && (empty($this->join_map) || $this->cacheJoins))
949 {
950 $ttl = $this->entity->getCacheTtl($this->cacheTtl);
951 }
952
953 if($ttl > 0)
954 {
955 $cacheId = md5($query);
956 $result = $this->entity->readFromCache($ttl, $cacheId, $this->countTotal);
957 }
958
959 if (!is_null($this->limit) && empty($this->limit))
960 {
961 // return empty result TODO
962
963 // or warning for now
964 /*trigger_error(sprintf(
965 'Limit must be different from %s',
966 var_export($this->limit, true)
967 ), E_USER_WARNING);*/
968 }
969
970 if($result === null)
971 {
972 $result = $this->query($query);
973
974 if($ttl > 0)
975 {
976 $result = $this->entity->writeToCache($result, $cacheId, $this->countTotal);
977 }
978 }
979
980 $this->is_executing = false;
981
982 $queryResult = new Result($this, $result);
983
984 if (!empty($this->forcedObjectPrimaryFields))
985 {
986 $queryResult->setHiddenObjectFields($this->forcedObjectPrimaryFields);
987 }
988
989 return $queryResult;
990 }
991
1001 public function fetch(\Bitrix\Main\Text\Converter $converter = null)
1002 {
1003 return $this->exec()->fetch($converter);
1004 }
1005
1015 public function fetchAll(\Bitrix\Main\Text\Converter $converter = null)
1016 {
1017 return $this->exec()->fetchAll($converter);
1018 }
1019
1028 public function fetchObject()
1029 {
1030 if (empty($this->select))
1031 {
1032 $this->addSelect('*');
1033 }
1034
1035 return $this->exec()->fetchObject();
1036 }
1037
1045 public function fetchCollection()
1046 {
1047 if (empty($this->select))
1048 {
1049 $this->addSelect('*');
1050 }
1051
1052 return $this->exec()->fetchCollection();
1053 }
1054
1055 protected function ensurePrimarySelect()
1056 {
1057 // no auto primary for queries with group
1058 // it may change the result
1059 if ($this->hasAggregation() || $this->hasDistinct())
1060 {
1061 return;
1062 }
1063
1064 $entities = [[$this->entity, '']];
1065
1066 foreach ($this->join_map as $join)
1067 {
1068 $entities[] = [$join['entity'], $join];
1069 }
1070
1071 // check for primaries in select
1072 foreach ($entities as list($entity, $join))
1073 {
1075 foreach ($entity->getPrimaryArray() as $primary)
1076 {
1077 if (!empty($entity->getField($primary)->hasParameter('auto_generated')))
1078 {
1079 continue;
1080 }
1081
1082 $needDefinition = !empty($join['definition']) ? $join['definition'].'.'.$primary : $primary;
1083
1084 $chain = $this->getRegisteredChain($needDefinition, true);
1085
1086 if (empty($this->select_chains[$chain->getAlias()]))
1087 {
1088 // set uniq alias
1089 $alias = $this->getUniqueAlias();
1090 $chain->setCustomAlias($alias);
1091
1092 $this->registerChain('select', $chain);
1093
1094 // remember to delete alias from array result
1095 $this->forcedObjectPrimaryFields[] = $alias;
1096
1097 // set join alias
1098 !empty($join)
1099 ? $chain->getLastElement()->setParameter('talias', $join['alias'])
1100 : $chain->getLastElement()->setParameter('talias', $this->getInitAlias());
1101 }
1102 }
1103 }
1104 }
1105
1114 protected function addToSelectChain($definition, $alias = null)
1115 {
1116 if ($definition instanceof ExpressionField)
1117 {
1118 if (empty($alias))
1119 {
1120 $alias = $definition->getName();
1121 }
1122
1123 $this->registerRuntimeField($alias, $definition);
1124 $chain = $this->getRegisteredChain($alias);
1125
1126 // add
1127 $this->registerChain('select', $chain);
1128
1129 // recursively collect all "build_from" fields
1130 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1131 {
1132 $this->collectExprChains($chain, array('hidden', 'select_expr'));
1133 }
1134 }
1135 elseif (is_array($definition))
1136 {
1137 // it is runtime field
1138 // now they are @deprecated in here
1139 throw new Main\ArgumentException(
1140 'Expression as an array in `select` section is no more supported due to security reason.'
1141 .' Please use `runtime` parameter, or Query->registerRuntimeField method, or pass ExpressionField object instead of array.'
1142 );
1143 }
1144 else
1145 {
1146 // localize definition (get last field segment e.g. NAME from REF1.REF2.NAME)
1147 $localDefinitionPos = strrpos($definition, '.');
1148
1149 if ($localDefinitionPos !== false)
1150 {
1151 $localDefinition = substr($definition, $localDefinitionPos + 1);
1152 $localEntityDef = substr($definition, 0, $localDefinitionPos);
1153 $localChain = Chain::getChainByDefinition($this->entity, $localEntityDef.'.*');
1154 $lastElemValue = $localChain->getLastElement()->getValue();
1155
1156 if ($lastElemValue instanceof Reference)
1157 {
1158 $localEntity = $lastElemValue->getRefEntity();
1159 }
1160 elseif (is_array($lastElemValue))
1161 {
1162 list($localEntity, ) = $lastElemValue;
1163 }
1164 else
1165 {
1166 $localEntity = $lastElemValue;
1167 }
1168 }
1169 else
1170 {
1171 $localDefinition = $definition;
1172 $localEntityDef = "";
1173 $dataClass = $this->entity->getDataClass();
1174 $localEntity = $dataClass::getEntity();
1175 }
1176
1177 // if there is a shell pattern in final segment, run recursively
1178 if ((strlen($localDefinition) > 1 && str_contains($localDefinition, '*'))
1179 || str_contains($localDefinition, '?')
1180 )
1181 {
1182 // get fields by pattern
1183 foreach ($localEntity->getFields() as $field)
1184 {
1185 if (
1186 ($field instanceof ScalarField || $field instanceof ExpressionField)
1187 && fnmatch($localDefinition, $field->getName())
1188 )
1189 {
1190 // skip private fields
1191 if ($field instanceof ScalarField && $field->isPrivate())
1192 {
1193 continue;
1194 }
1195
1196 // skip uf utm single
1197 if (
1198 str_starts_with($field->getName(), 'UF_') && str_ends_with($field->getName(), '_SINGLE')
1199 && $localEntity->hasField(substr($field->getName(), 0, -7))
1200 )
1201 {
1202 continue;
1203 }
1204
1205
1206 // build alias
1207 $customAlias = null;
1208
1209 if ($alias !== null)
1210 {
1211 // put alias as a prefix
1212 $customAlias = $alias.$field->getName();
1213 }
1214
1215 // build definition
1216 $fieldDefinition = $field->getName();
1217
1218 if (!empty($localEntityDef))
1219 {
1220 $fieldDefinition = $localEntityDef.'.'.$fieldDefinition;
1221 }
1222
1223 $this->addToSelectChain($fieldDefinition, $customAlias);
1224 }
1225 }
1226
1227 return $this;
1228 }
1229
1230 // there is normal scalar field, or Reference, or Entity (all fields of)
1231 $chain = $this->getRegisteredChain($definition, true);
1232
1233 if ($alias !== null)
1234 {
1235 // custom alias
1236 $chain = clone $chain;
1237 $chain->setCustomAlias($alias);
1238 }
1239
1240 $last_elem = $chain->getLastElement();
1241
1242 // fill if element is not scalar
1244 $expand_entity = null;
1245
1246 if ($last_elem->getValue() instanceof Reference)
1247 {
1248 $expand_entity = $last_elem->getValue()->getRefEntity();
1249 }
1250 elseif (is_array($last_elem->getValue()))
1251 {
1252 list($expand_entity, ) = $last_elem->getValue();
1253 }
1254 elseif ($last_elem->getValue() instanceof Entity)
1255 {
1256 $expand_entity = $last_elem->getValue();
1257 }
1258 elseif ($last_elem->getValue() instanceof OneToMany)
1259 {
1260 $expand_entity = $last_elem->getValue()->getRefEntity();
1261 }
1262 elseif ($last_elem->getValue() instanceof ManyToMany)
1263 {
1264 $expand_entity = $last_elem->getValue()->getRefEntity();
1265 }
1266
1267 if (!$expand_entity && $alias !== null)
1268 {
1269 // we have a single field, let's check its custom alias
1270 if (
1271 $this->entity->hasField($alias)
1272 && (
1273 // if it's not the same field
1274 $this->entity->getFullName() !== $last_elem->getValue()->getEntity()->getFullName()
1275 ||
1276 $last_elem->getValue()->getName() !== $alias
1277 )
1278 )
1279 {
1280 // deny aliases eq. existing fields
1281 throw new Main\ArgumentException(sprintf(
1282 'Alias "%s" matches already existing field "%s" of initial entity "%s". '.
1283 'Please choose another name for alias.',
1284 $alias, $alias, $this->entity->getFullName()
1285 ));
1286 }
1287 }
1288
1289 if ($expand_entity)
1290 {
1291 // add all fields of entity
1292 foreach ($expand_entity->getFields() as $exp_field)
1293 {
1294 // except for references and expressions
1295 if ($exp_field instanceof ScalarField)
1296 {
1297 // skip private fields
1298 if ($exp_field->isPrivate())
1299 {
1300 continue;
1301 }
1302
1303 $exp_chain = clone $chain;
1304 $exp_chain->addElement(new ChainElement(
1305 $exp_field
1306 ));
1307
1308 // custom alias
1309 if ($alias !== null)
1310 {
1311 $fieldAlias = $alias . $exp_field->getName();
1312
1313 // deny aliases eq. existing fields
1314 if ($this->entity->hasField($fieldAlias))
1315 {
1316 throw new Main\ArgumentException(sprintf(
1317 'Alias "%s" + field "%s" match already existing field "%s" of initial entity "%s". '.
1318 'Please choose another name for alias.',
1319 $alias, $exp_field->getName(), $fieldAlias, $this->entity->getFullName()
1320 ));
1321 }
1322
1323 $exp_chain->setCustomAlias($fieldAlias);
1324 }
1325
1326 // add
1327 $this->registerChain('select', $exp_chain);
1328 }
1329 }
1330 }
1331 else
1332 {
1333 // scalar field that defined in entity
1334 $this->registerChain('select', $chain);
1335
1336 // it would be nice here to register field as a runtime when it has custom alias
1337 // it will make possible to use aliased fields as a native init entity fields
1338 // e.g. in expressions or in data_doubling=off filter
1339
1340 // collect buildFrom fields (recursively)
1341 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1342 {
1343 $this->collectExprChains($chain, array('hidden', 'select_expr'));
1344 }
1345 }
1346 }
1347
1348 return $this;
1349 }
1350
1358 public function setFilterChains(&$filter, $section = 'filter')
1359 {
1360 foreach ($filter as $filter_def => &$filter_match)
1361 {
1362 if ($filter_def === 'LOGIC')
1363 {
1364 continue;
1365 }
1366
1367 if (!is_numeric($filter_def))
1368 {
1369 $sqlWhere = new \CSQLWhere();
1370 $csw_result = $sqlWhere->makeOperation($filter_def);
1371 list($definition, ) = array_values($csw_result);
1372
1373 // do not register it in global chain registry - get it in a smuggled way
1374 // - we will do the registration later after UF rewriting and data doubling checking
1375 $chain = $this->getRegisteredChain($definition);
1376
1377 if (!$chain)
1378 {
1379 // try to find it in filter chains if it is 2nd call of method (when dividing filter for where/having)
1380 // and chain is still not registered in global (e.g. when forcesDataDoublingOff)
1381 $chain = $this->filter_chains[$definition] ?? Chain::getChainByDefinition($this->entity, $definition);
1382 }
1383
1384 // dirty hack for UF multiple fields: replace text UF_SMTH by UF_SMTH_SINGLE
1385 $dstField = $chain->getLastElement()->getValue();
1386 $dstEntity = $dstField->getEntity();
1387
1388 if ($dstField instanceof ExpressionField && count($dstField->getBuildFromChains()) == 1)
1389 {
1390 // hold entity, but get real closing field
1391 $dstBuildFromChains = $dstField->getBuildFromChains();
1392
1393 $firstChain = $dstBuildFromChains[0];
1394 $dstField = $firstChain->getLastElement()->getValue();
1395 }
1396
1397 // check for base linking
1398 if (($dstField instanceof TextField || $dstField instanceof ArrayField)
1399 && $dstEntity->hasField($dstField->getName().'_SINGLE'))
1400 {
1401 $utmLinkField = $dstEntity->getField($dstField->getName().'_SINGLE');
1402
1403 if ($utmLinkField instanceof ExpressionField)
1404 {
1405 $buildFromChains = $utmLinkField->getBuildFromChains();
1406
1407 // check for back-reference
1408 if (count($buildFromChains) == 1 && $buildFromChains[0]->hasBackReference())
1409 {
1410 $endField = $buildFromChains[0]->getLastElement()->getValue();
1411
1412 // and final check for entity name
1413 if(strpos($endField->getEntity()->getName(), 'Utm'))
1414 {
1415 $expressionChain = clone $chain;
1416 $expressionChain->removeLastElement();
1417 $expressionChain->addElement(new ChainElement(clone $utmLinkField));
1418 $expressionChain->forceDataDoublingOff();
1419
1420 $chain = $expressionChain;
1421
1422 // rewrite filter definition
1423 unset($filter[$filter_def]);
1424 $filter[$filter_def.'_SINGLE'] = $filter_match;
1425 $definition .= '_SINGLE';
1426 }
1427 }
1428 }
1429 }
1430
1431 // continue
1432 $registerChain = true;
1433
1434 // if data doubling disabled, and it is back-reference - do not register, it will be overwritten
1435 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1436 {
1437 $registerChain = false;
1438 }
1439
1440 if ($registerChain)
1441 {
1442 $this->registerChain($section, $chain, $definition);
1443
1444 // fill hidden select
1445 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1446 {
1447 $this->collectExprChains($chain);
1448 }
1449 }
1450 else
1451 {
1452 // hide from global registry to avoid "join table"
1453 // but we still need it in filter chains
1454 $this->filter_chains[$chain->getAlias()] = $chain;
1455 $this->filter_chains[$definition] = $chain;
1456
1457 // and we will need primary chain in filter later when overwriting data-doubling
1458 $this->getRegisteredChain($this->entity->getPrimary(), true);
1459 }
1460 }
1461 elseif (is_array($filter_match))
1462 {
1463 $this->setFilterChains($filter_match, $section);
1464 }
1465 }
1466 }
1467
1475 public function setFilterHandlerChains(Filter $where, $section = 'filter')
1476 {
1477 foreach ($where->getConditions() as $condition)
1478 {
1479 if ($condition instanceof Filter)
1480 {
1481 // subfilter
1482 $this->setFilterHandlerChains($condition, $section);
1483 }
1484 else
1485 {
1486 $definition = $condition->getDefinition();
1487
1488 // check for runtime fields
1489 if ($definition instanceof Field)
1490 {
1491 // register runtime field
1492 $this->registerRuntimeField($definition);
1493
1494 // rewrite definition in filter - replace field with its name
1495 $definition = $definition->getName();
1496 $condition->setDefinition($definition);
1497 }
1498
1499 // check if it's a regular condition, not kind of boolean/exists expression
1500 if ($definition !== null)
1501 {
1502 // regular condition
1503 $chain = $this->getRegisteredChain($definition);
1504
1505 if (!$chain)
1506 {
1507 // try to find it in filter chains if it is 2nd call of method (when dividing filter for where/having)
1508 // and chain is still not registered in global (e.g. when forcesDataDoublingOff)
1509 $chain = $this->filter_chains[$definition] ?? Chain::getChainByDefinition($this->entity, $definition);
1510 }
1511
1512 // dirty hack for UF multiple fields: replace text UF_SMTH by UF_SMTH_SINGLE
1513 $dstField = $chain->getLastElement()->getValue();
1514 $dstEntity = $dstField->getEntity();
1515
1516 if ($dstField instanceof ExpressionField && count($dstField->getBuildFromChains()) == 1)
1517 {
1518 // hold entity, but get real closing field
1519 $dstBuildFromChains = $dstField->getBuildFromChains();
1520
1521 $firstChain = $dstBuildFromChains[0];
1522 $dstField = $firstChain->getLastElement()->getValue();
1523 }
1524
1525 // check for base linking
1526 if (($dstField instanceof TextField || $dstField instanceof ArrayField)
1527 && $dstEntity->hasField($dstField->getName().'_SINGLE'))
1528 {
1529 $utmLinkField = $dstEntity->getField($dstField->getName().'_SINGLE');
1530
1531 if ($utmLinkField instanceof ExpressionField)
1532 {
1533 $buildFromChains = $utmLinkField->getBuildFromChains();
1534
1535 // check for back-reference
1536 if (count($buildFromChains) == 1 && $buildFromChains[0]->hasBackReference())
1537 {
1538 $endField = $buildFromChains[0]->getLastElement()->getValue();
1539
1540 // and final check for entity name
1541 if(strpos($endField->getEntity()->getName(), 'Utm'))
1542 {
1543 $expressionChain = clone $chain;
1544 $expressionChain->removeLastElement();
1545 $expressionChain->addElement(new ChainElement(clone $utmLinkField));
1546 $expressionChain->forceDataDoublingOff();
1547
1548 $chain = $expressionChain;
1549
1550 // rewrite filter definition
1551 $definition .= '_SINGLE';
1552 $condition->setDefinition($definition);
1553 }
1554 }
1555 }
1556 }
1557
1558 // continue
1559 $registerChain = true;
1560
1561 // if data doubling disabled, and it is back-reference - do not register, it will be overwritten
1562 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1563 {
1564 $registerChain = false;
1565 }
1566
1567 if ($registerChain)
1568 {
1569 $this->registerChain($section, $chain, $definition);
1570
1571 // fill hidden select
1572 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1573 {
1574 $this->collectExprChains($chain);
1575 }
1576 }
1577 else
1578 {
1579 // hide from global registry to avoid "join table"
1580 // but we still need it in filter chains
1581 $this->filter_chains[$chain->getAlias()] = $chain;
1582 $this->filter_chains[$definition] = $chain;
1583
1584 // and we will need primary chain in filter later when overwriting data-doubling
1585 $this->getRegisteredChain($this->entity->getPrimary(), true);
1586 }
1587 }
1588
1589 // when compare with column, put it in the chains too
1590 foreach ($condition->getAtomicValues() as $value)
1591 {
1592 if ($value instanceof ColumnExpression)
1593 {
1594 $valueDefinition = $value->getDefinition();
1595
1596 $chain = $this->filter_chains[$valueDefinition] ?? Chain::getChainByDefinition($this->entity, $valueDefinition);
1597
1598 $this->registerChain($section, $chain, $valueDefinition);
1599 }
1600
1601 // set connection to correct escaping in expressions
1602 if ($value instanceof Main\DB\SqlExpression)
1603 {
1604 $value->setConnection($this->entity->getConnection());
1605 }
1606 }
1607 }
1608 }
1609 }
1610
1615 protected function divideFilter()
1616 {
1617 // divide filter to where and having
1618
1619 $logic = $this->filter['LOGIC'] ?? 'AND';
1620
1621 if ($logic == 'OR')
1622 {
1623 // if it has aggr then move all to having
1624 if ($this->checkFilterAggregation($this->filter))
1625 {
1626 $this->where = array();
1627 $this->where_chains = array();
1628
1629 $this->having = $this->filter;
1630 $this->having_chains = $this->filter_chains;
1631 }
1632 else
1633 {
1634 $this->where = $this->filter;
1635 $this->where_chains = $this->filter_chains;
1636
1637 $this->having = array();
1638 $this->having_chains = array();
1639 }
1640 }
1641 elseif ($logic == 'AND')
1642 {
1643 // we can separate root filters
1644 foreach ($this->filter as $k => $sub_filter)
1645 {
1646 if ($k === 'LOGIC')
1647 {
1648 $this->where[$k] = $sub_filter;
1649 $this->having[$k] = $sub_filter;
1650
1651 continue;
1652 }
1653
1654 $tmp_filter = array($k => $sub_filter);
1655
1656 if ($this->checkFilterAggregation($tmp_filter))
1657 {
1658 $this->having[$k] = $sub_filter;
1659 $this->setFilterChains($tmp_filter, 'having');
1660 }
1661 else
1662 {
1663 $this->where[$k] = $sub_filter;
1664 $this->setFilterChains($tmp_filter, 'where');
1665 }
1666 }
1667 }
1668
1669 // collect "build_from" fields from having
1670 foreach ($this->having_chains as $chain)
1671 {
1672 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1673 {
1674 $this->collectExprChains($chain, array('hidden', 'having_expr'));
1675 }
1676 }
1677 }
1678
1683 protected function divideFilterHandler()
1684 {
1685 $logic = $this->filterHandler->logic();
1686
1687 if ($logic == 'or')
1688 {
1689 // if it has aggr then move all to having
1690 if ($this->checkFilterHandlerAggregation($this->filterHandler))
1691 {
1692 $this->havingHandler = $this->filterHandler;
1693 $this->having_chains = $this->filter_chains;
1694 }
1695 else
1696 {
1697 $this->whereHandler = $this->filterHandler;
1698 $this->where_chains = $this->filter_chains;
1699 }
1700 }
1701 elseif ($logic == 'and')
1702 {
1703 // we can separate root filters
1704 foreach ($this->filterHandler->getConditions() as $condition)
1705 {
1706 $tmpFilter = static::filter()->addCondition($condition);
1707
1708 if ($this->checkFilterHandlerAggregation($tmpFilter))
1709 {
1710 $this->havingHandler->addCondition($tmpFilter);
1711 $this->setFilterHandlerChains($tmpFilter, 'having');
1712 }
1713 else
1714 {
1715 $this->whereHandler->addCondition($condition);
1716 $this->setFilterHandlerChains($tmpFilter, 'where');
1717 }
1718 }
1719 }
1720
1721 // collect "build_from" fields from having
1722 foreach ($this->having_chains as $chain)
1723 {
1724 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1725 {
1726 $this->collectExprChains($chain, array('hidden', 'having_expr'));
1727 }
1728 }
1729 }
1730
1738 {
1739 foreach ($filter as $filter_def => $filter_match)
1740 {
1741 if ($filter_def === 'LOGIC')
1742 {
1743 continue;
1744 }
1745
1746 $is_having = false;
1747 if (!is_numeric($filter_def))
1748 {
1749 $sqlWhere = new \CSQLWhere();
1750 $csw_result = $sqlWhere->makeOperation($filter_def);
1751 list($definition, ) = array_values($csw_result);
1752
1753 $chain = $this->filter_chains[$definition];
1754 $last = $chain->getLastElement();
1755
1756 $is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
1757 }
1758 elseif (is_array($filter_match))
1759 {
1760 $is_having = $this->checkFilterAggregation($filter_match);
1761 }
1762
1763 if ($is_having)
1764 {
1765 return true;
1766 }
1767 }
1768
1769 return false;
1770 }
1771
1779 {
1780 foreach ($filter->getConditions() as $condition)
1781 {
1782 $is_having = false;
1783
1784 if ($condition instanceof Filter)
1785 {
1786 // subfilter
1787 $is_having = $this->checkFilterHandlerAggregation($condition);
1788 }
1789 else
1790 {
1791 // check if it is not a boolean/exists condition
1792 if ($condition->getDefinition() !== null)
1793 {
1794 // regular condition
1795 $chain = $this->filter_chains[$condition->getDefinition()];
1796 $last = $chain->getLastElement();
1797
1798 $is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
1799
1800 // check if value is a field and has aggregation
1801 if (!$is_having && $condition->getValue() instanceof ColumnExpression)
1802 {
1803 $chain = $this->filter_chains[$condition->getValue()->getDefinition()];
1804 $last = $chain->getLastElement();
1805
1806 $is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
1807
1808 // actually if it has happened, we need to add group by the first column
1809 }
1810 }
1811 }
1812
1813 if ($is_having)
1814 {
1815 return true;
1816 }
1817 }
1818
1819 return false;
1820 }
1821
1829 protected function rewriteDataDoubling(Filter $filter, $section)
1830 {
1831 foreach ($filter->getConditions() as $condition)
1832 {
1833 if ($condition instanceof Filter)
1834 {
1835 //subfilter
1836 $this->rewriteDataDoubling($condition, $section);
1837 }
1838 elseif ($condition->getDefinition() !== null)
1839 {
1840 // regular condition
1841 $chain = $this->filter_chains[$condition->getDefinition()];
1842
1843 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1844 {
1845 $primaryName = $this->entity->getPrimary();
1846 $uniquePostfix = '_TMP'.rand();
1847
1848 // build subquery
1849 $dataClass = $this->entity->getDataClass();
1850
1851 $subQuery = $dataClass::query()
1852 ->addSelect($primaryName)
1853 ->where(clone $condition)
1854 ->setTableAliasPostfix(strtolower($uniquePostfix));
1855
1856 // change condition
1857 $condition->setColumn($primaryName);
1858 $condition->setOperator('in');
1859 $condition->setValue($subQuery);
1860
1861 // register primary's chain
1862 $idChain = $this->getRegisteredChain($primaryName);
1863 $this->registerChain($section, $idChain, $primaryName);
1864 }
1865 }
1866 }
1867 }
1868
1874 protected function addToGroupChain($definition)
1875 {
1876 $chain = $this->getRegisteredChain($definition, true);
1877 $this->registerChain('group', $chain);
1878
1879 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1880 {
1881 $this->collectExprChains($chain);
1882 }
1883 }
1884
1890 protected function addToOrderChain($definition)
1891 {
1892 $chain = $this->getRegisteredChain($definition, true);
1893 $this->registerChain('order', $chain);
1894
1895 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1896 {
1897 $this->collectExprChains($chain);
1898 }
1899
1900 if ($this->is_distinct)
1901 {
1902 // make sure field is in select while distinct
1903 $this->addToSelectChain($definition);
1904 }
1905 }
1906
1913 protected function buildJoinMap($chains = null)
1914 {
1915 $connection = $this->entity->getConnection();
1916 $helper = $connection->getSqlHelper();
1917
1918 $aliasLength = $helper->getAliasLength();
1919
1920 if (empty($chains))
1921 {
1922 $chains = $this->global_chains;
1923 }
1924
1925 foreach ($chains as $chain)
1926 {
1927 if ($chain->getLastElement()->getParameter('talias'))
1928 {
1929 // already been here
1930 continue;
1931 }
1932
1933 // in NO_DOUBLING mode skip 1:N relations that presented in filter only
1934 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1935 {
1936 $alias = $chain->getAlias();
1937
1938 if (isset($this->filter_chains[$alias])
1939 && !isset($this->select_chains[$alias]) && !isset($this->select_expr_chains[$alias])
1940 && !isset($this->group_chains[$alias]) && !isset($this->order_chains[$alias])
1941 )
1942 {
1943 continue;
1944 }
1945 }
1946
1947 $prev_alias = $this->getInitAlias(false);
1948
1949 $map_key = '';
1950
1955 $elements = array_slice($chain->getAllElements(), 1);
1956
1957 $currentDefinition = array();
1958
1959 foreach ($elements as $element)
1960 {
1961 $table_alias = null;
1962
1968 if ($element->getValue() instanceof Reference)
1969 {
1970 // ref to another entity
1971 $ref_field = $element->getValue();
1972 $dst_entity = $ref_field->getRefEntity();
1973 $joinType = $ref_field->getJoinType();
1974 }
1975 elseif (is_array($element->getValue()))
1976 {
1977 // link from another entity to this
1978 list($dst_entity, $ref_field) = $element->getValue();
1979 $joinType = $ref_field->getJoinType();
1980 }
1981 elseif ($element->getValue() instanceof OneToMany)
1982 {
1983 // the same as back reference
1984 $dst_entity = $element->getValue()->getRefEntity();
1985 $ref_field = $element->getValue()->getRefField();
1986 $joinType = $element->getValue()->getJoinType() ?: $ref_field->getJoinType();
1987 }
1988 elseif ($element->getValue() instanceof ManyToMany)
1989 {
1990 $mtm = $element->getValue();
1991
1992 // join mediator and remote entities in hidden mode
1993 // first, make new chain, remove everything after this mtm and remove mtm itself
1994 $tmpChain = clone $chain;
1995 $mtmDefinition = join('.', $currentDefinition);
1996
1997 while ($tmpChain->getDefinition() != $mtmDefinition)
1998 {
1999 $tmpChain->removeLastElement();
2000 }
2001
2002 // then add backReference to mediator - mediator entity and local reference
2003 $tmpChain->addElement(new ChainElement([
2004 $mtm->getMediatorEntity(), $mtm->getLocalReference()
2005 ]));
2006
2007 // then add reference from mediator to remote entity
2008 $tmpChain->addElement(new ChainElement($mtm->getRemoteReference()));
2009
2010 // now join this chain
2011 $this->registerChain('global', $tmpChain);
2012 $this->buildJoinMap([$tmpChain]);
2013
2014 // and finally remember table alias for mtm element
2015 $prev_alias = $tmpChain->getLastElement()->getParameter('talias');
2016 $element->setParameter('talias', $prev_alias);
2017
2018 // save mapping for next elements and skip later
2019 $ref_field = $element->getValue();
2020 $dst_entity = $element->getValue()->getRefEntity();
2021 }
2022 else
2023 {
2024 // scalar field
2025 // if it's a field of the init entity, use getInitAlias to use 'base' alias
2026 if ($prev_alias === $this->getInitAlias(false))
2027 {
2028 $element->setParameter('talias', $this->getInitAlias());
2029 }
2030 else
2031 {
2032 $element->setParameter('talias', $prev_alias.$this->table_alias_postfix);
2033 }
2034
2035 continue;
2036 }
2037
2038 // mapping
2039 if (empty($map_key))
2040 {
2041 $map_key = join('.', $currentDefinition);
2042 }
2043
2044 $map_key .= '/' . $ref_field->getName() . '/' . $dst_entity->getName();
2045
2046 $currentDefinition[] = $element->getDefinitionFragment();
2047
2048 // skip any further actions for ManyToMany as long as it has its own buildJoinMap call
2049 if ($element->getValue() instanceof ManyToMany)
2050 {
2051 // also replace definition from mediator.ref to mtm
2052 $lastKey = array_key_last($this->join_map);
2053 $this->join_map[$lastKey]['definition'] = join('.', $currentDefinition);
2054
2055 continue;
2056 }
2057
2058 if (isset($this->join_registry[$map_key]))
2059 {
2060 // already connected
2061 $table_alias = $this->join_registry[$map_key];
2062 }
2063 else
2064 {
2065 // prepare reference
2066 $reference = $ref_field->getReference();
2067
2068 if ($element->getValue() instanceof Reference)
2069 {
2070 // ref to another entity
2071 if (is_null($table_alias))
2072 {
2073 $table_alias = $prev_alias.'_'.strtolower($ref_field->getName());
2074
2075 if (strlen($table_alias.$this->table_alias_postfix) > $aliasLength)
2076 {
2077 $old_table_alias = $table_alias;
2078 $table_alias = 'TALIAS_' . (count($this->replaced_taliases) + 1);
2079 $this->replaced_taliases[$table_alias] = $old_table_alias;
2080 }
2081 }
2082
2083 $alias_this = $prev_alias;
2084 $alias_ref = $table_alias;
2085
2086 $isBackReference = false;
2087
2088 $definition_this = join('.', array_slice($currentDefinition, 0, -1));
2089 $definition_ref = join('.', $currentDefinition);
2090 $definition_join = $definition_ref;
2091 }
2092 elseif (is_array($element->getValue()) || $element->getValue() instanceof OneToMany)
2093 {
2094 if (is_null($table_alias))
2095 {
2096 $table_alias = StringHelper::camel2snake($dst_entity->getName()).'_'.strtolower($ref_field->getName());
2097 $table_alias = $prev_alias.'_'.$table_alias;
2098
2099 if (strlen($table_alias.$this->table_alias_postfix) > $aliasLength)
2100 {
2101 $old_table_alias = $table_alias;
2102 $table_alias = 'TALIAS_' . (count($this->replaced_taliases) + 1);
2103 $this->replaced_taliases[$table_alias] = $old_table_alias;
2104 }
2105 }
2106
2107 $alias_this = $table_alias;
2108 $alias_ref = $prev_alias;
2109
2110 $isBackReference = true;
2111
2112 $definition_this = join('.', $currentDefinition);
2113 $definition_ref = join('.', array_slice($currentDefinition, 0, -1));
2114 $definition_join = $definition_this;
2115 }
2116 else
2117 {
2118 throw new Main\SystemException(sprintf('Unknown reference element `%s`', $element->getValue()));
2119 }
2120
2121 // replace this. and ref. to real definition
2122 if ($reference instanceof Filter)
2123 {
2124 $csw_reference = $this->prepareJoinFilterReference(
2125 $reference,
2126 $alias_this.$this->table_alias_postfix,
2127 $alias_ref.$this->table_alias_postfix,
2128 $definition_this,
2129 $definition_ref,
2130 $isBackReference
2131 );
2132 }
2133 else
2134 {
2135 $csw_reference = $this->prepareJoinReference(
2136 $reference,
2137 $alias_this.$this->table_alias_postfix,
2138 $alias_ref.$this->table_alias_postfix,
2139 $definition_this,
2140 $definition_ref,
2141 $isBackReference
2142 );
2143 }
2144
2145 // double check after recursive call in prepareJoinReference
2146 if (!isset($this->join_registry[$map_key]))
2147 {
2148 $join = array(
2149 'type' => $joinType,
2150 'entity' => $dst_entity,
2151 'definition' => $definition_join,
2152 'table' => $dst_entity->getDBTableName(),
2153 'alias' => $table_alias.$this->table_alias_postfix,
2154 'reference' => $csw_reference,
2155 'map_key' => $map_key
2156 );
2157
2158 $this->join_map[] = $join;
2159 $this->join_registry[$map_key] = $table_alias;
2160 }
2161 }
2162
2163 // set alias for each element
2164 $element->setParameter('talias', $table_alias.$this->table_alias_postfix);
2165
2166 $prev_alias = $table_alias;
2167 }
2168 }
2169 }
2170
2171 protected function buildSelect()
2172 {
2173 $sql = [];
2174
2175 $helper = $this->entity->getConnection()->getSqlHelper();
2176 $aliasLength = (int) $helper->getAliasLength();
2177
2178 foreach ($this->select_chains as $chain)
2179 {
2180 $definition = $chain->getSqlDefinition();
2181 $alias = $chain->getAlias();
2182
2183 if (strlen($alias) > $aliasLength)
2184 {
2185 // replace long aliases
2186 $newAlias = 'FALIAS_'.count($this->replaced_aliases);
2187 $this->replaced_aliases[$newAlias] = $alias;
2188
2189 $alias = $newAlias;
2190 }
2191
2192 $sql[] = $definition . ' AS ' . $helper->quote($alias);
2193 }
2194
2195 // empty select (or select forced primary only)
2196 if (empty($sql) ||
2197 (!empty($this->forcedObjectPrimaryFields) && count($sql) == count($this->forcedObjectPrimaryFields))
2198 )
2199 {
2200 $sql[] = 1;
2201 }
2202
2203 $strSql = join(",\n\t", $sql);
2204
2205 if ($this->hasDistinct() && $this->is_distinct)
2206 {
2207 // distinct by query settings, not by field
2208 $strSql = 'DISTINCT '.$strSql;
2209 }
2210
2211 return "\n\t".$strSql;
2212 }
2213
2218 protected function buildJoin()
2219 {
2220 $sql = array();
2221 $csw = new \CSQLWhere;
2222
2223 $connection = $this->entity->getConnection();
2224 $helper = $connection->getSqlHelper();
2225
2226 foreach ($this->join_map as $join)
2227 {
2228 // prepare csw fields
2229 $csw_fields = $this->getJoinCswFields($join['reference']);
2230 $csw->setFields($csw_fields);
2231
2232 if ($join['reference'] instanceof Filter)
2233 {
2234 $joinConditionSql = $join['reference']->getSql($this->global_chains);
2235 }
2236 else
2237 {
2238 $joinConditionSql = trim($csw->getQuery($join['reference']));
2239 }
2240
2241 // final sql
2242 $sql[] = sprintf('%s JOIN %s %s ON %s',
2243 $join['type'],
2244 $this->quoteTableSource($join['table']),
2245 $helper->quote($join['alias']),
2246 $joinConditionSql
2247 );
2248 }
2249
2250 return "\n".join("\n", $sql);
2251 }
2252
2258 protected function buildWhere()
2259 {
2260 $sql = array();
2261
2262 // old array filter
2263 if (!empty($this->where))
2264 {
2265 $csw = new \CSQLWhere;
2266
2267 $csw_fields = $this->getFilterCswFields($this->where);
2268 $csw->setFields($csw_fields);
2269
2270 $sql[] = trim($csw->getQuery($this->where));
2271 }
2272
2273 // new QueryFilter
2274 if ($this->whereHandler && $this->whereHandler->hasConditions())
2275 {
2276 // rewrite data doubling
2277 $this->rewriteDataDoubling($this->whereHandler, 'where');
2278
2279 $sql[] = $this->whereHandler->getSql($this->where_chains);
2280 }
2281
2282 return join(' AND ', array_filter($sql));
2283 }
2284
2289 protected function buildGroup()
2290 {
2291 $sql = array();
2292
2293 if ($this->hasAggregation())
2294 {
2295 // add non-aggr fields to group
2296 foreach ($this->global_chains as $chain)
2297 {
2298 $alias = $chain->getAlias();
2299
2300 // skip constants
2301 if ($chain->isConstant())
2302 {
2303 continue;
2304 }
2305
2306 if (isset($this->select_chains[$alias]) || isset($this->order_chains[$alias]) || isset($this->having_chains[$alias]))
2307 {
2308 if (isset($this->group_chains[$alias]))
2309 {
2310 // skip already grouped
2311 continue;
2312 }
2313 elseif (!$chain->hasAggregation() && !$chain->hasSubquery())
2314 {
2315 // skip subqueries and already aggregated
2316 $this->registerChain('group', $chain);
2317 }
2318 elseif (!$chain->hasAggregation() && $chain->hasSubquery() && $chain->getLastElement()->getValue() instanceof ExpressionField)
2319 {
2320 // but include build_from of subqueries
2321 $sub_chains = $chain->getLastElement()->getValue()->getBuildFromChains();
2322
2323 foreach ($sub_chains as $sub_chain)
2324 {
2325 // build real subchain starting from init entity
2326 $real_sub_chain = clone $chain;
2327
2328 foreach (array_slice($sub_chain->getAllElements(), 1) as $sub_chain_elem)
2329 {
2330 $real_sub_chain->addElement($sub_chain_elem);
2331 }
2332
2333 // add to query
2334 $this->registerChain('group', $this->global_chains[$real_sub_chain->getAlias()]);
2335 }
2336 }
2337 }
2338 elseif (isset($this->having_expr_chains[$alias]))
2339 {
2340 if (!$chain->hasAggregation() && $chain->hasSubquery())
2341 {
2342 $this->registerChain('group', $chain);
2343 }
2344 }
2345 }
2346 }
2347
2348 foreach ($this->group_chains as $chain)
2349 {
2350 $connection = $this->entity->getConnection();
2351 $sqlDefinition = $chain->getSqlDefinition();
2352 $valueField = $chain->getLastElement()->getValue();
2353
2354 if ($valueField instanceof ExpressionField)
2355 {
2356 $valueField = $valueField->getValueField();
2357 }
2358
2359 if (($connection instanceof Main\DB\OracleConnection || $connection instanceof Main\DB\MssqlConnection)
2360 && $valueField instanceof TextField)
2361 {
2362 // softTextCast
2363 $sqlDefinition = $connection->getSqlHelper()->softCastTextToChar($sqlDefinition);
2364 }
2365
2366 $sql[] = $sqlDefinition;
2367 }
2368
2369 return join(', ', $sql);
2370 }
2371
2377 protected function buildHaving()
2378 {
2379 $sql = array();
2380
2381 // old array filter
2382 if (!empty($this->having))
2383 {
2384 $csw = new \CSQLWhere;
2385
2386 $csw_fields = $this->getFilterCswFields($this->having);
2387 $csw->setFields($csw_fields);
2388
2389 $sql[] = trim($csw->getQuery($this->having));
2390 }
2391
2392 // new QueryFilter
2393 if ($this->havingHandler && $this->havingHandler->hasConditions())
2394 {
2395 // rewrite data doubling
2396 $this->rewriteDataDoubling($this->havingHandler, 'having');
2397
2398 $sql[] = $this->havingHandler->getSql($this->having_chains);
2399 }
2400
2401 return join(' AND ', array_filter($sql));
2402 }
2403
2408 protected function buildOrder()
2409 {
2410 $sql = array();
2411
2412 foreach ($this->order_chains as $chain)
2413 {
2414 $sort = isset($this->order[$chain->getDefinition()])
2415 ? $this->order[$chain->getDefinition()]
2416 : ($this->order[$chain->getAlias()] ?? '');
2417
2418 $connection = $this->entity->getConnection();
2419
2420 // define value field
2421 $valueField = $chain->getLastElement()->getValue();
2422 if ($valueField instanceof ExpressionField)
2423 {
2424 $valueField = $valueField->getValueField();
2425 }
2426
2427 // get final sql definition
2428 if (isset($this->select_chains[$chain->getAlias()]))
2429 {
2430 // optimization for fields that are in select already
2431 $alias = $chain->getAlias();
2432
2433 if ($key = array_search($alias, $this->replaced_aliases))
2434 {
2435 // alias was replaced
2436 $alias = $key;
2437 }
2438
2439 $sqlDefinition = $connection->getSqlHelper()->quote($alias);
2440 }
2441 else
2442 {
2443 $sqlDefinition = $chain->getSqlDefinition();
2444 }
2445
2446 if (($connection instanceof Main\DB\OracleConnection || $connection instanceof Main\DB\MssqlConnection)
2447 && $valueField instanceof TextField)
2448 {
2449 // softTextCast
2450 $sqlDefinition = $connection->getSqlHelper()->softCastTextToChar($sqlDefinition);
2451 }
2452
2453 $sql[] = $sqlDefinition. ' ' . $sort;
2454 }
2455
2456 return join(', ', $sql);
2457 }
2458
2466 protected function buildQuery($forceObjectPrimary = true)
2467 {
2468 $connection = $this->entity->getConnection();
2469 $helper = $connection->getSqlHelper();
2470
2471 if ($this->query_build_parts === null)
2472 {
2473
2474 foreach ($this->select as $key => $value)
2475 {
2476 $this->addToSelectChain($value, is_numeric($key) ? null : $key);
2477 }
2478
2479 $this->setFilterChains($this->filter);
2480 $this->divideFilter();
2481
2482 // unconditional entity scope
2483 $this->entity->setDefaultScope($this);
2484
2485 $this->setFilterHandlerChains($this->filterHandler);
2486 $this->divideFilterHandler();
2487
2488 foreach ($this->group as $value)
2489 {
2490 $this->addToGroupChain($value);
2491 }
2492
2493 foreach ($this->order as $key => $value)
2494 {
2495 $this->addToOrderChain($key);
2496 }
2497
2498 $this->buildJoinMap();
2499
2500 if ($forceObjectPrimary && empty($this->unionHandler))
2501 {
2502 $this->ensurePrimarySelect();
2503 }
2504
2505 $sqlJoin = $this->buildJoin();
2506
2507 $sqlSelect = $this->buildSelect();
2508 $sqlWhere = $this->buildWhere();
2509 $sqlGroup = $this->buildGroup();
2510 $sqlHaving = $this->buildHaving();
2511 $sqlOrder = $this->buildOrder();
2512
2513 $sqlFrom = $this->quoteTableSource($this->entity->getDBTableName());
2514
2515 $sqlFrom .= ' '.$helper->quote($this->getInitAlias());
2516 $sqlFrom .= ' '.$sqlJoin;
2517
2518 $this->query_build_parts = array_filter(array(
2519 'SELECT' => $sqlSelect,
2520 'FROM' => $sqlFrom,
2521 'WHERE' => $sqlWhere,
2522 'GROUP BY' => $sqlGroup,
2523 'HAVING' => $sqlHaving,
2524 'ORDER BY' => $sqlOrder
2525 ));
2526
2527 // ensure there are no private fields in query
2528 $this->checkForPrivateFields();
2529 }
2530
2531 $build_parts = $this->query_build_parts;
2532
2533 foreach ($build_parts as $k => &$v)
2534 {
2535 $v = $k . ' ' . $v;
2536 }
2537
2538 $query = join("\n", $build_parts);
2539
2540 if ($this->limit > 0)
2541 {
2542 $query = $helper->getTopSql($query, $this->limit, $this->offset);
2543 }
2544
2545 // union
2546 if (!empty($this->unionHandler))
2547 {
2548 if ($this->order || $this->limit)
2549 {
2550 $query = "({$query})";
2551 }
2552
2553 foreach ($this->unionHandler->getQueries() as $union)
2554 {
2555 $query .= " ".$union->getSql();
2556 }
2557
2558 // union sort
2559 if ($this->unionHandler->getOrder())
2560 {
2561 $sqlUnionOrder = array();
2562 foreach ($this->unionHandler->getOrder() as $definition => $sort)
2563 {
2564 $sqlDefinition = $connection->getSqlHelper()->quote(
2565 $this->global_chains[$definition]->getAlias()
2566 );
2567
2568 $sqlUnionOrder[] = $sqlDefinition . ' ' . $sort;
2569 }
2570
2571 $query .= ' ORDER BY ' . join(', ', $sqlUnionOrder);
2572 }
2573
2574 // union limit
2575 if ($this->unionHandler->getLimit())
2576 {
2577 $query = $helper->getTopSql($query, $this->unionHandler->getLimit(), $this->unionHandler->getOffset());
2578 }
2579 }
2580
2581 return $query;
2582 }
2583
2591 protected function getFilterCswFields(&$filter)
2592 {
2593 $fields = array();
2594
2595 foreach ($filter as $filter_def => &$filter_match)
2596 {
2597 if ($filter_def === 'LOGIC')
2598 {
2599 continue;
2600 }
2601
2602 if (!is_numeric($filter_def))
2603 {
2604 $sqlWhere = new \CSQLWhere();
2605 $csw_result = $sqlWhere->makeOperation($filter_def);
2606 list($definition, $operation) = array_values($csw_result);
2607
2608 $chain = $this->filter_chains[$definition];
2609 $last = $chain->getLastElement();
2610
2611 // need to create an alternative of CSQLWhere in D7.Entity
2612 $field_type = $last->getValue()->getDataType();
2613 $callback = null;
2614
2615 // rewrite type & value for CSQLWhere
2616 if (in_array($operation, array('SE', 'SN'), true)
2617 && in_array($filter_match, array(null, true, false), true)
2618 )
2619 {
2620 $field_type = 'callback';
2621
2622 if ($filter_match === null)
2623 {
2624 $callback = array($this, 'nullEqualityCallback');
2625 }
2626 else
2627 {
2628 // just boolean expression, without operator
2629 // e.g. WHERE EXISTS(...)
2630 $callback = array($this, 'booleanStrongEqualityCallback');
2631 }
2632 }
2633 elseif ($field_type == 'integer')
2634 {
2635 $field_type = 'int';
2636 }
2637 elseif ($field_type == 'boolean')
2638 {
2639 $field_type = 'string';
2640
2642 $field = $last->getValue();
2643 $values = $field->getValues();
2644
2645 if (is_numeric($values[0]) && is_numeric($values[1]))
2646 {
2647 $field_type = 'int';
2648 }
2649
2650 if (is_scalar($filter_match))
2651 {
2652 $filter_match = $field->normalizeValue($filter_match);
2653 }
2654 }
2655 elseif ($field_type == 'float' || $field_type == 'decimal')
2656 {
2657 $field_type = 'double';
2658 }
2659 elseif ($field_type == 'enum' || $field_type == 'text')
2660 {
2661 $field_type = 'string';
2662 }
2663
2664 $sqlDefinition = $chain->getSqlDefinition();
2665
2666 // data-doubling-off mode
2668 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
2669 {
2670 $primaryName = $this->entity->getPrimary();
2671 $uniquePostfix = '_TMP'.rand();
2672
2673 // build subquery
2674 $subQuery = new Query($this->entity);
2675 $subQuery->addSelect($primaryName);
2676 $subQuery->addFilter($filter_def, $filter_match);
2677 $subQuery->setTableAliasPostfix(strtolower($uniquePostfix));
2678 $subQuerySql = $subQuery->getQuery();
2679
2680 // proxying subquery as value to callback
2681 $filter_match = $subQuerySql;
2682 $callback = array($this, 'dataDoublingCallback');
2683
2684 $field_type = 'callback';
2685
2686 // change sql definition
2687 $idChain = $this->getRegisteredChain($primaryName);
2688 $sqlDefinition = $idChain->getSqlDefinition();
2689 }
2690
2691 // set entity connection to the sql expressions
2692 if ($filter_match instanceof Main\DB\SqlExpression)
2693 {
2694 $filter_match->setConnection($this->entity->getConnection());
2695 }
2696
2697 //$is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
2698
2699 // if back-reference found (Entity:REF)
2700 // if NO_DOUBLING mode enabled, then change getSQLDefinition to subquery exists(...)
2701 // and those chains should not be in joins if it is possible
2702
2703 /*if (!$this->data_doubling && $chain->hasBackReference())
2704 {
2705 $field_type = 'callback';
2706 $init_query = $this;
2707
2708 $callback = function ($field, $operation, $value) use ($init_query, $chain)
2709 {
2710 $init_entity = $init_query->getEntity();
2711 $init_table_alias = CBaseEntity::camel2snake($init_entity->getName()).$init_query->getTableAliasPostfix();
2712
2713 $filter = array();
2714
2715 // add primary linking with main query
2716 foreach ($init_entity->getPrimaryArray() as $primary)
2717 {
2718 $filter['='.$primary] = new CSQLWhereExpression('?#', $init_table_alias.'.'.$primary);
2719 }
2720
2721 // add value filter
2722 $filter[CSQLWhere::getOperationByCode($operation).$chain->getDefinition()] = $value;
2723
2724 // build subquery
2725 $query_class = __CLASS__;
2726 $sub_query = new $query_class($init_entity);
2727 $sub_query->setFilter($filter);
2728 $sub_query->setTableAliasPostfix('_sub');
2729
2730 return 'EXISTS(' . $sub_query->getQuery() . ')';
2731 };
2732 }*/
2733
2734 $fields[$definition] = array(
2735 'TABLE_ALIAS' => 'table',
2736 'FIELD_NAME' => $sqlDefinition,
2737 'FIELD_TYPE' => $field_type,
2738 'MULTIPLE' => '',
2739 'JOIN' => '',
2740 'CALLBACK' => $callback
2741 );
2742 }
2743 elseif (is_array($filter_match))
2744 {
2745 $fields = array_merge($fields, $this->getFilterCswFields($filter_match));
2746 }
2747 }
2748
2749 return $fields;
2750 }
2751
2764 protected function prepareJoinReference($reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference)
2765 {
2766 $new = array();
2767
2768 foreach ($reference as $k => $v)
2769 {
2770 if ($k === 'LOGIC')
2771 {
2772 $new[$k] = $v;
2773 continue;
2774 }
2775
2776 if (is_numeric($k))
2777 {
2778 // subfilter, recursive call
2779 $new[$k] = $this->prepareJoinReference($v, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference);
2780 }
2781 else
2782 {
2783 // key
2784 $sqlWhere = new \CSQLWhere();
2785 $csw_result = $sqlWhere->makeOperation($k);
2786 list($field, $operation) = array_values($csw_result);
2787
2788 if (str_starts_with($field, 'this.'))
2789 {
2790 // parse the chain
2791 $definition = str_replace(\CSQLWhere::getOperationByCode($operation).'this.', '', $k);
2792 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
2793
2794 $chain = $this->getRegisteredChain($absDefinition, true);
2795
2796 if (!$isBackReference)
2797 {
2798 // make sure these fields will be joined before the main join
2799 $this->buildJoinMap(array($chain));
2800 }
2801 else
2802 {
2803 $chain->getLastElement()->setParameter('talias', $alias_this);
2804 }
2805
2806 // recursively collect all "build_from" fields
2807 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2808 {
2809 $this->collectExprChains($chain);
2810 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
2811
2812 foreach ($buildFrom as $bf)
2813 {
2814 // set base chain
2815 $baseChain = clone $chain;
2816
2817 // remove the last one - expression itself
2818 $baseChain->removeLastElement();
2819
2820 // remove parent entity for this child
2821 $bf->removeFirstElement();
2822
2823 // set new parents
2824 $bf->prepend($baseChain);
2825 }
2826
2827 $this->buildJoinMap($buildFrom);
2828 }
2829
2830 $k = \CSQLWhere::getOperationByCode($operation).$chain->getSqlDefinition();
2831 }
2832 elseif (str_starts_with($field, 'ref.'))
2833 {
2834 $definition = str_replace(\CSQLWhere::getOperationByCode($operation).'ref.', '', $k);
2835
2836 if (str_contains($definition, '.'))
2837 {
2838 throw new Main\ArgumentException(sprintf(
2839 'Reference chain `%s` is not allowed here. First-level definitions only.', $field
2840 ));
2841 }
2842
2843 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
2844 $chain = $this->getRegisteredChain($absDefinition, true);
2845
2846 if ($isBackReference)
2847 {
2848 // make sure these fields will be joined before the main join
2849 $this->buildJoinMap(array($chain));
2850 }
2851 else
2852 {
2853 $chain->getLastElement()->setParameter('talias', $alias_ref);
2854 }
2855
2856 // recursively collect all "build_from" fields
2857 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2858 {
2859 $this->collectExprChains($chain);
2860 $this->buildJoinMap($chain->getLastElement()->getValue()->getBuildFromChains());
2861 }
2862
2863 $k = \CSQLWhere::getOperationByCode($operation).$chain->getSqlDefinition();
2864 }
2865 else
2866 {
2867 throw new Main\SystemException(sprintf('Unknown reference key `%s`, it should start with "this." or "ref."', $k));
2868 }
2869
2870 // value
2871 if (is_array($v))
2872 {
2873 // field = expression
2874 $v = new \CSQLWhereExpression($v[0], array_slice($v, 1));
2875 }
2876 elseif ($v instanceof Main\DB\SqlExpression)
2877 {
2878 // set entity connection
2879 $v->setConnection($this->entity->getConnection());
2880 }
2881 elseif (!is_object($v))
2882 {
2883 if (str_starts_with($v, 'this.'))
2884 {
2885 $definition = str_replace('this.', '', $v);
2886 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
2887
2888 $chain = $this->getRegisteredChain($absDefinition, true);
2889
2890 if (!$isBackReference)
2891 {
2892 // make sure these fields will be joined before the main join
2893 $this->buildJoinMap(array($chain));
2894 }
2895 else
2896 {
2897 $chain->getLastElement()->setParameter('talias', $alias_this);
2898 }
2899
2900 // recursively collect all "build_from" fields
2901 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2902 {
2903 $this->collectExprChains($chain);
2904 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
2905
2906 foreach ($buildFrom as $bf)
2907 {
2908 // set base chain
2909 $baseChain = clone $chain;
2910
2911 // remove the last one - expression itself
2912 $baseChain->removeLastElement();
2913
2914 // remove parent entity for this child
2915 $bf->removeFirstElement();
2916
2917 // set new parents
2918 $bf->prepend($baseChain);
2919 }
2920
2921 $this->buildJoinMap($buildFrom);
2922 }
2923
2924 $field_def = $chain->getSqlDefinition();
2925 }
2926 elseif (str_starts_with($v, 'ref.'))
2927 {
2928 $definition = str_replace('ref.', '', $v);
2929
2930 if (str_contains($definition, '.'))
2931 {
2932 throw new Main\ArgumentException(sprintf(
2933 'Reference chain `%s` is not allowed here. First-level definitions only.', $v
2934 ));
2935 }
2936
2937 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
2938 $chain = $this->getRegisteredChain($absDefinition, true);
2939
2940 if ($isBackReference)
2941 {
2942 // make sure these fields will be joined before the main join
2943 $this->buildJoinMap(array($chain));
2944 }
2945 else
2946 {
2947 $chain->getLastElement()->setParameter('talias', $alias_ref);
2948 }
2949
2950 $this->buildJoinMap(array($chain));
2951
2952 // recursively collect all "build_from" fields
2953 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2954 {
2955 // here could be one more check "First-level definitions only" for buildFrom elements
2956 $buildFromChains = $this->collectExprChains($chain);
2957
2958 // set same talias to buildFrom elements
2959 foreach ($buildFromChains as $buildFromChain)
2960 {
2961 if (!$isBackReference && $buildFromChain->getSize() > $chain->getSize())
2962 {
2963 throw new Main\ArgumentException(sprintf(
2964 'Reference chain `%s` is not allowed here. First-level definitions only.',
2965 $buildFromChain->getDefinition()
2966 ));
2967 }
2968
2969 if ($buildFromChain->getSize() === $chain->getSize())
2970 {
2971 // same entity, same table
2972 $buildFromChain->getLastElement()->setParameter('talias', $alias_ref);
2973 }
2974 }
2975
2976 $this->buildJoinMap($buildFromChains);
2977 }
2978
2979 $field_def = $chain->getSqlDefinition();
2980 }
2981 else
2982 {
2983 throw new Main\SystemException(sprintf('Unknown reference value `%s`', $v));
2984 }
2985
2986 $v = new \CSQLWhereExpression($field_def);
2987 }
2988 else
2989 {
2990 throw new Main\SystemException(sprintf('Unknown reference value `%s`, it should start with "this." or "ref."', $v));
2991 }
2992
2993 $new[$k] = $v;
2994 }
2995 }
2996
2997 return $new;
2998 }
2999
3013 protected function prepareJoinFilterReference(Filter $reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference, $firstCall = true)
3014 {
3015 // do not make an impact on original reference object
3016 if ($firstCall)
3017 {
3018 $reference = clone $reference;
3019 }
3020
3021 foreach ($reference->getConditions() as $condition)
3022 {
3023 if ($condition instanceof Filter)
3024 {
3025 // subfilter, recursive call
3027 $condition,
3028 $alias_this,
3029 $alias_ref,
3030 $baseDefinition,
3031 $refDefinition,
3032 $isBackReference,
3033 false
3034 );
3035 }
3036 else
3037 {
3038 // regular condition
3039 $field = $condition->getDefinition();
3040
3041 if (str_starts_with($field, 'this.'))
3042 {
3043 // parse the chain
3044 $definition = str_replace('this.', '', $field);
3045 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
3046
3047 $chain = $this->getRegisteredChain($absDefinition, true);
3048
3049 if (!$isBackReference)
3050 {
3051 // make sure these fields will be joined before the main join
3052 $this->buildJoinMap(array($chain));
3053 }
3054 else
3055 {
3056 $chain->getLastElement()->setParameter('talias', $alias_this);
3057 }
3058
3059 // recursively collect all "build_from" fields
3060 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3061 {
3062 $this->collectExprChains($chain);
3063 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
3064
3065 foreach ($buildFrom as $bf)
3066 {
3067 // set base chain
3068 $baseChain = clone $chain;
3069
3070 // remove the last one - expression itself
3071 $baseChain->removeLastElement();
3072
3073 // remove parent entity for this child
3074 $bf->removeFirstElement();
3075
3076 // set new parents
3077 $bf->prepend($baseChain);
3078 }
3079
3080 $this->buildJoinMap($buildFrom);
3081 }
3082
3083 $condition->setColumn($absDefinition);
3084 }
3085 elseif (str_starts_with($field, 'ref.'))
3086 {
3087 $definition = str_replace('ref.', '', $field);
3088
3089 if (str_contains($definition, '.'))
3090 {
3091 throw new Main\ArgumentException(sprintf(
3092 'Reference chain `%s` is not allowed here. First-level definitions only.', $field
3093 ));
3094 }
3095
3096 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
3097 $chain = $this->getRegisteredChain($absDefinition, true);
3098
3099 if ($isBackReference)
3100 {
3101 // make sure these fields will be joined before the main join
3102 $this->buildJoinMap(array($chain));
3103 }
3104 else
3105 {
3106 $chain->getLastElement()->setParameter('talias', $alias_ref);
3107 }
3108
3109 // recursively collect all "build_from" fields
3110 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3111 {
3112 $this->collectExprChains($chain);
3113 $this->buildJoinMap($chain->getLastElement()->getValue()->getBuildFromChains());
3114 }
3115
3116 $condition->setColumn($absDefinition);
3117 }
3118 else
3119 {
3120 throw new Main\SystemException(sprintf('Unknown reference key `%s`, it should start with "this." or "ref."', $field));
3121 }
3122
3123 // value
3124 $v = $condition->getValue();
3125
3126 if ($v instanceof Main\DB\SqlExpression)
3127 {
3128 // set entity connection
3129 $v->setConnection($this->entity->getConnection());
3130 }
3131 elseif ($v instanceof ColumnExpression)
3132 {
3133 if (str_starts_with($v->getDefinition(), 'this.'))
3134 {
3135 $definition = str_replace('this.', '', $v->getDefinition());
3136 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
3137
3138 $chain = $this->getRegisteredChain($absDefinition, true);
3139
3140 if (!$isBackReference)
3141 {
3142 // make sure these fields will be joined before the main join
3143 $this->buildJoinMap(array($chain));
3144 }
3145 else
3146 {
3147 $chain->getLastElement()->setParameter('talias', $alias_this);
3148 }
3149
3150 // recursively collect all "build_from" fields
3151 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3152 {
3153 $this->collectExprChains($chain);
3154 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
3155
3156 foreach ($buildFrom as $bf)
3157 {
3158 // set base chain
3159 $baseChain = clone $chain;
3160
3161 // remove the last one - expression itself
3162 $baseChain->removeLastElement();
3163
3164 // remove parent entity for this child
3165 $bf->removeFirstElement();
3166
3167 // set new parents
3168 $bf->prepend($baseChain);
3169 }
3170
3171 $this->buildJoinMap($buildFrom);
3172 }
3173
3174 $v->setDefinition($absDefinition);
3175 }
3176 elseif (str_starts_with($v->getDefinition(), 'ref.'))
3177 {
3178 $definition = str_replace('ref.', '', $v->getDefinition());
3179
3180 if (str_contains($definition, '.'))
3181 {
3182 throw new Main\ArgumentException(sprintf(
3183 'Reference chain `%s` is not allowed here. First-level definitions only.', $v->getDefinition()
3184 ));
3185 }
3186
3187 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
3188 $chain = $this->getRegisteredChain($absDefinition, true);
3189
3190 if ($isBackReference)
3191 {
3192 // make sure these fields will be joined before the main join
3193 $this->buildJoinMap(array($chain));
3194 }
3195 else
3196 {
3197 $chain->getLastElement()->setParameter('talias', $alias_ref);
3198 }
3199
3200 $this->buildJoinMap(array($chain));
3201
3202 // recursively collect all "build_from" fields
3203 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3204 {
3205 // here could be one more check "First-level definitions only" for buildFrom elements
3206 $buildFromChains = $this->collectExprChains($chain);
3207
3208 // set same talias to buildFrom elements
3209 foreach ($buildFromChains as $buildFromChain)
3210 {
3211 if (!$isBackReference && $buildFromChain->getSize() > $chain->getSize())
3212 {
3213 throw new Main\ArgumentException(sprintf(
3214 'Reference chain `%s` is not allowed here. First-level definitions only.',
3215 $buildFromChain->getDefinition()
3216 ));
3217 }
3218
3219 if ($buildFromChain->getSize() === $chain->getSize())
3220 {
3221 // same entity, same table
3222 $buildFromChain->getLastElement()->setParameter('talias', $alias_ref);
3223 }
3224 }
3225
3226 $this->buildJoinMap($buildFromChains);
3227 }
3228
3229 $v->setDefinition($absDefinition);
3230 }
3231 }
3232 }
3233 }
3234
3235 return $reference;
3236 }
3237
3238 protected function getJoinCswFields($reference)
3239 {
3240 $fields = array();
3241
3242 foreach ($reference as $k => $v)
3243 {
3244 if ($k === 'LOGIC')
3245 {
3246 continue;
3247 }
3248
3249 if (is_numeric($k))
3250 {
3251 $fields = array_merge($fields, $this->getJoinCswFields($v));
3252 }
3253 else
3254 {
3255 // key
3256 $sqlWhere = new \CSQLWhere();
3257 $csw_result = $sqlWhere->makeOperation($k);
3258 list($field, ) = array_values($csw_result);
3259
3260 $fields[$field] = array(
3261 'TABLE_ALIAS' => 'alias',
3262 'FIELD_NAME' => $field,
3263 'FIELD_TYPE' => 'string',
3264 'MULTIPLE' => '',
3265 'JOIN' => ''
3266 );
3267
3268 // no need to add values as csw fields
3269 }
3270 }
3271
3272 return $fields;
3273 }
3274
3281 protected function checkChainsAggregation($chain)
3282 {
3284 $chains = is_array($chain) ? $chain : array($chain);
3285
3286 foreach ($chains as $chain)
3287 {
3288 $last = $chain->getLastElement();
3289 $is_aggr = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
3290
3291 if ($is_aggr)
3292 {
3293 return true;
3294 }
3295 }
3296
3297 return false;
3298 }
3299
3300 protected function checkChainsDistinct($chain)
3301 {
3303 $chains = is_array($chain) ? $chain : array($chain);
3304
3305 foreach ($chains as $chain)
3306 {
3307 $field = $chain->getLastElement()->getValue();
3308
3309 if ($field instanceof ExpressionField)
3310 {
3311 $expression = $field->getFullExpression();
3312 $expression = ExpressionField::removeSubqueries($expression);
3313
3314 preg_match_all('/(?:^|[^a-z0-9_])(DISTINCT)[\s(]+/i', $expression, $matches);
3315
3316 if (!empty($matches[1]))
3317 {
3318 return true;
3319 }
3320 }
3321 }
3322
3323 return false;
3324 }
3325
3326 public function hasAggregation()
3327 {
3328 return !empty($this->group_chains) || !empty($this->having_chains)
3329 || $this->checkChainsAggregation($this->select_chains)
3330 || $this->checkChainsAggregation($this->order_chains);
3331 }
3332
3333 public function setDistinct($distinct = true)
3334 {
3335 $this->is_distinct = (bool) $distinct;
3336
3337 return $this;
3338 }
3339
3340 public function hasDistinct()
3341 {
3342 $distinctInSelect = $this->checkChainsDistinct($this->select_chains);
3343
3344 if ($distinctInSelect && $this->is_distinct)
3345 {
3346 // to avoid double distinct
3347 $this->is_distinct = false;
3348 }
3349
3350 return ($distinctInSelect || $this->is_distinct);
3351 }
3352
3362 protected function collectExprChains(Chain $chain, $storages = array('hidden'))
3363 {
3364 $last_elem = $chain->getLastElement();
3365 $bf_chains = $last_elem->getValue()->getBuildFromChains();
3366
3367 $pre_chain = clone $chain;
3368 //$pre_chain->removeLastElement();
3369 $scopedBuildFrom = [];
3370
3371 foreach ($bf_chains as $bf_chain)
3372 {
3373 // collect hidden chain
3374 $tmp_chain = clone $pre_chain;
3375
3376 // exclude init entity
3378 $bf_elements = array_slice($bf_chain->getAllElements(), 1);
3379
3380 // add elements
3381 foreach ($bf_elements as $bf_element)
3382 {
3383 $tmp_chain->addElement($bf_element);
3384 }
3385
3386 //if (!($bf_chain->getLastElement()->getValue() instanceof ExpressionField))
3387 {
3388 foreach ($storages as $storage)
3389 {
3390 $reg_chain = $this->registerChain($storage, $tmp_chain);
3391 }
3392
3393 // replace "build_from" chain end by registered chain end
3394 // actually it's better and more correctly to replace the whole chain
3395 $bf_chain->removeLastElement();
3396 $bf_chain->addElement($reg_chain->getLastElement());
3397
3398 // return buildFrom elements with original start of chain for this query
3399 $scoped_bf_chain = clone $pre_chain;
3400 $scoped_bf_chain->removeLastElement();
3401
3402 // copy tail from registered chain
3403 $tail = array_slice($reg_chain->getAllElements(), $pre_chain->getSize());
3404
3405 foreach ($tail as $tailElement)
3406 {
3407 $scoped_bf_chain->addElement($tailElement);
3408 }
3409
3410 $scopedBuildFrom[] = $scoped_bf_chain;
3411 }
3412
3413 // check elements to recursive collect hidden chains
3414 foreach ($bf_elements as $bf_element)
3415 {
3416 if ($bf_element->getValue() instanceof ExpressionField)
3417 {
3418 $this->collectExprChains($tmp_chain);
3419 }
3420 }
3421 }
3422
3423 return $scopedBuildFrom;
3424 }
3425
3430 protected function getUnionHandler()
3431 {
3432 if ($this->unionHandler === null)
3433 {
3434 $this->unionHandler = new Union($this->entity->getConnection());
3435 }
3436
3437 return $this->unionHandler;
3438 }
3439
3440 public function registerChain($section, Chain $chain, $opt_key = null)
3441 {
3442 $alias = $chain->getAlias();
3443
3444 if (isset($this->global_chains[$alias]))
3445 {
3446 if ($this->global_chains[$alias]->getDefinition() == $chain->getDefinition())
3447 {
3448 $reg_chain = $this->global_chains[$alias];
3449 }
3450 else
3451 {
3452 // we have a collision
3453 // like book.author_id and book.author.id have the same aliases, but different definitions
3454 // in most of the cases it's not a problem, there would be the same expected data,
3455 // but we need register this chain separately to be available for internal usage
3456 $reg_chain = $chain;
3457
3458 $this->global_chains[$reg_chain->getDefinition()] = $chain;
3459
3460 // or should we make unique alias and register with it?
3461 $alias = $this->getUniqueAlias();
3462 $chain->setCustomAlias($alias);
3463 $this->global_chains[$alias] = $chain;
3464 }
3465 }
3466 else
3467 {
3468 $reg_chain = $chain;
3469 $def = $reg_chain->getDefinition();
3470
3471 $this->global_chains[$alias] = $chain;
3472 $this->global_chains[$def] = $chain;
3473 }
3474
3475 $storage_name = $section . '_chains';
3476
3477 // in case of collision do not rewrite by alias
3478 if (!isset($this->{$storage_name}[$alias]))
3479 {
3480 $this->{$storage_name}[$alias] = $reg_chain;
3481 // should we store by definition too?
3482 }
3483
3484 if (!is_null($opt_key))
3485 {
3486 $this->{$storage_name}[$opt_key] = $reg_chain;
3487 }
3488
3489 return $reg_chain;
3490 }
3491
3500 public function getRegisteredChain($key, $force_create = false)
3501 {
3502 if (isset($this->global_chains[$key]))
3503 {
3504 return $this->global_chains[$key];
3505 }
3506
3507 if ($force_create)
3508 {
3509 $chain = Chain::getChainByDefinition($this->entity, $key);
3510 $this->registerChain('global', $chain);
3511
3512 return $chain;
3513 }
3514
3515 return false;
3516 }
3517
3518 protected function getUniqueAlias()
3519 {
3520 return 'UALIAS_'.($this->uniqueAliasCounter++);
3521 }
3522
3523 public function booleanStrongEqualityCallback($field, $operation, $value)
3524 {
3525 $value = ($operation == 'SE') ? $value : !$value;
3526 return ($value ? '' : 'NOT ') . $field;
3527 }
3528
3529 public function nullEqualityCallback($field, $operation, $value)
3530 {
3531 return $field.' IS '.($operation == 'SE' ? '' : 'NOT ') . 'NULL';
3532 }
3533
3534 public function dataDoublingCallback($field, $operation, $value)
3535 {
3536 return $field.' IN ('.$value.')';
3537 }
3538
3547 protected function query($query)
3548 {
3549 // check nosql configuration
3550 $connection = $this->entity->getConnection();
3551 $configuration = $connection->getConfiguration();
3552
3554 $result = null;
3555
3556 if (isset($configuration['handlersocket']['read']))
3557 {
3558 // optimize through nosql
3559 $nosqlConnectionName = $configuration['handlersocket']['read'];
3560
3561 $nosqlConnection = Main\Application::getInstance()->getConnectionPool()->getConnection($nosqlConnectionName);
3562 $isNosqlCapable = NosqlPrimarySelector::checkQuery($nosqlConnection, $this);
3563
3564 if ($isNosqlCapable)
3565 {
3566 $nosqlResult = NosqlPrimarySelector::relayQuery($nosqlConnection, $this);
3567 $result = new Main\DB\ArrayResult($nosqlResult);
3568
3569 // add data converters
3570 if (!empty($nosqlResult))
3571 {
3573 $converters = [];
3574
3575 foreach ($this->getSelectChains() as $selectChain)
3576 {
3577 $field = $selectChain->getLastElement()->getValue();
3578
3579 if ($field instanceof ScalarField)
3580 {
3581 $converter = $connection->getSqlHelper()->getConverter($field);
3582
3583 if (is_callable($converter))
3584 {
3585 $converter[$selectChain->getAlias()] = $converter;
3586 }
3587 }
3588 }
3589
3590 if (!empty($converters))
3591 {
3592 $result->setConverters($converters);
3593 }
3594 }
3595 }
3596 }
3597
3598 if ($result === null)
3599 {
3600 // regular SQL query
3601 $result = $connection->query($query);
3602 $result->setReplacedAliases($this->replaced_aliases);
3603
3604 if($this->countTotal)
3605 {
3606 if ($this->limit && ($result->getSelectedRowsCount() < $this->limit))
3607 {
3608 // optimization for first and last pages
3609 $result->setCount((int) $this->offset + $result->getSelectedRowsCount());
3610 }
3611 elseif (empty($this->limit))
3612 {
3613 // optimization for queries without limit
3614 $result->setCount($result->getSelectedRowsCount());
3615 }
3616 else
3617 {
3618 // dedicated query
3619 $result->setCount($this->queryCountTotal());
3620 }
3621 }
3622
3623 static::$last_query = $query;
3624 }
3625
3626 if ($this->isFetchModificationRequired())
3627 {
3628 $result->addFetchDataModifier(array($this, 'fetchDataModificationCallback'));
3629 }
3630
3631 return $result;
3632 }
3633
3634 public function queryCountTotal()
3635 {
3636 if ($this->query_build_parts === null)
3637 {
3638 $this->buildQuery();
3639 }
3640
3641 $buildParts = $this->query_build_parts;
3642
3643 //remove order
3644 unset($buildParts['ORDER BY']);
3645
3646 //remove select
3647 $buildParts['SELECT'] = "1 cntholder";
3648
3649 foreach ($buildParts as $k => &$v)
3650 {
3651 $v = $k . ' ' . $v;
3652 }
3653
3654 $cntQuery = join("\n", $buildParts);
3655
3656 // select count
3657 $cntQuery =
3658 "SELECT COUNT(cntholder) AS TMP_ROWS_CNT FROM ({$cntQuery}) xxx";
3659
3660 return $this->entity->getConnection()->queryScalar($cntQuery);
3661 }
3662
3668 {
3669 // entity-defined callbacks
3670 foreach ($this->selectFetchModifiers as $alias => $modifiers)
3671 {
3672 foreach ($modifiers as $modifier)
3673 {
3674 $data[$alias] = call_user_func_array($modifier, array($data[$alias], $this, $data, $alias));
3675 }
3676 }
3677 }
3678
3685 {
3686 $this->selectFetchModifiers = array();
3687
3688 foreach ($this->select_chains as $chain)
3689 {
3690 if ($chain->getLastElement()->getValue()->getFetchDataModifiers())
3691 {
3692 $this->selectFetchModifiers[$chain->getAlias()] = $chain->getLastElement()->getValue()->getFetchDataModifiers();
3693 }
3694 }
3695
3696 return !empty($this->selectFetchModifiers) || !empty($this->files);
3697 }
3698
3706 protected function replaceSelectAliases($query)
3707 {
3708 $connection = $this->entity->getConnection();
3709 $helper = $connection->getSqlHelper();
3710
3711 $length = (int) $helper->getAliasLength();
3712 $leftQuote = $helper->getLeftQuote();
3713 $rightQuote = $helper->getRightQuote();
3714
3715 $replaced = array();
3716
3717 preg_match_all(
3718 '/ AS '.preg_quote($leftQuote).'([a-z0-9_]{'.($length+1).',})'.preg_quote($rightQuote).'/i',
3720 );
3721
3722 if (!empty($matches[1]))
3723 {
3724 foreach ($matches[1] as $alias)
3725 {
3726 $newAlias = 'FALIAS_'.count($replaced);
3727 $replaced[$newAlias] = $alias;
3728
3729 $query = str_replace(
3730 ' AS ' . $helper->quote($alias),
3731 ' AS ' . $helper->quote($newAlias) . '/* '.$alias.' */',
3732 $query
3733 );
3734 }
3735 }
3736
3737 return array($query, $replaced);
3738 }
3739
3746 public function quoteTableSource($source)
3747 {
3748 // don't quote subqueries
3749 if (!preg_match('/\s*\‍(\s*SELECT.*\‍)\s*/is', $source))
3750 {
3751 $source = $this->entity->getConnection()->getSqlHelper()->quote($source);
3752 }
3753
3754 return $source;
3755 }
3756
3757 public function __clone()
3758 {
3759 $this->entity = clone $this->entity;
3760
3761 $this->filterHandler = clone $this->filterHandler;
3762 $this->whereHandler = clone $this->whereHandler;
3763 $this->havingHandler = clone $this->havingHandler;
3764
3765 foreach ($this->select as $k => $v)
3766 {
3767 if ($v instanceof ExpressionField)
3768 {
3769 $this->select[$k] = clone $v;
3770 }
3771 }
3772 }
3773
3778 public function hasBackReference()
3779 {
3780 if (empty($this->global_chains))
3781 {
3782 throw new Main\SystemException('Query has not been executed or built');
3783 }
3784
3785 foreach ($this->global_chains as $chain)
3786 {
3787 if ($chain->hasBackReference())
3788 {
3789 return true;
3790 }
3791 }
3792
3793 return false;
3794 }
3795
3799 public function getChains()
3800 {
3801 return $this->global_chains;
3802 }
3803
3807 public function getGroupChains()
3808 {
3809 return $this->group_chains;
3810 }
3811
3815 public function getHiddenChains()
3816 {
3817 return $this->hidden_chains;
3818 }
3819
3823 public function getHavingChains()
3824 {
3825 return $this->having_chains;
3826 }
3827
3831 public function getFilterChains()
3832 {
3833 return $this->filter_chains;
3834 }
3835
3839 public function getOrderChains()
3840 {
3841 return $this->order_chains;
3842 }
3843
3847 public function getSelectChains()
3848 {
3849 return $this->select_chains;
3850 }
3851
3855 public function getWhereChains()
3856 {
3857 return $this->where_chains;
3858 }
3859
3863 public function getRuntimeChains()
3864 {
3865 return $this->runtime_chains;
3866 }
3867
3868 public function getJoinMap()
3869 {
3870 return $this->join_map;
3871 }
3872
3882 public function getQuery($forceObjectPrimary = false)
3883 {
3884 return $this->buildQuery($forceObjectPrimary);
3885 }
3886
3892 public static function getLastQuery()
3893 {
3894 return static::$last_query;
3895 }
3896
3897 public function getEntity()
3898 {
3899 return $this->entity;
3900 }
3901
3913 public static function buildFilterSql(Entity $entity, $filter)
3914 {
3915 $query = new static($entity);
3916
3917 if ($filter instanceof Filter)
3918 {
3919 // new object filter
3920 $query->where($filter);
3921 }
3922 else
3923 {
3924 // old array filter
3925 $query->setFilter($filter);
3926 }
3927
3928 $query->setCustomBaseTableAlias($entity->getDBTableName())->buildQuery();
3929
3930 return $query->query_build_parts['WHERE'];
3931 }
3932
3939 public function getInitAlias($withPostfix = true)
3940 {
3941 if ($this->custom_base_table_alias !== null)
3942 {
3943 return $this->custom_base_table_alias;
3944 }
3945
3946 $init_alias = strtolower($this->entity->getCode());
3947
3948 // add postfix
3949 if ($withPostfix)
3950 {
3951 $init_alias .= $this->table_alias_postfix;
3952 }
3953
3954 // check length
3955 $connection = $this->entity->getConnection();
3956 $aliasLength = $connection->getSqlHelper()->getAliasLength();
3957
3958 if (strlen($init_alias) > $aliasLength)
3959 {
3960 $init_alias = 'base';
3961
3962 // add postfix
3963 if ($withPostfix)
3964 {
3965 $init_alias .= $this->table_alias_postfix;
3966 }
3967 }
3968
3969 return $init_alias;
3970 }
3971
3972 public function getReplacedAliases()
3973 {
3974 return $this->replaced_aliases;
3975 }
3976
3977 /*
3978 * Sets cache TTL in seconds.
3979 * @param int $ttl
3980 * @return $this
3981 */
3982 public function setCacheTtl($ttl)
3983 {
3984 $this->cacheTtl = (int)$ttl;
3985 return $this;
3986 }
3987
3993 public function cacheJoins($mode)
3994 {
3995 $this->cacheJoins = (bool)$mode;
3996 return $this;
3997 }
3998
3999 public function dump()
4000 {
4001 echo '<pre>';
4002
4003 echo 'last query: ';
4004 var_dump(static::$last_query);
4005 echo PHP_EOL;
4006
4007 echo 'size of select_chains: '.count($this->select_chains);
4008 echo PHP_EOL;
4009 foreach ($this->select_chains as $num => $chain)
4010 {
4011 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4012 $chain->dump();
4013 echo PHP_EOL;
4014 }
4015
4016 echo PHP_EOL.PHP_EOL;
4017
4018 echo 'size of where_chains: '.count($this->where_chains);
4019 echo PHP_EOL;
4020 foreach ($this->where_chains as $num => $chain)
4021 {
4022 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4023 $chain->dump();
4024 echo PHP_EOL;
4025 }
4026
4027 echo PHP_EOL.PHP_EOL;
4028
4029 echo 'size of group_chains: '.count($this->group_chains);
4030 echo PHP_EOL;
4031 foreach ($this->group_chains as $num => $chain)
4032 {
4033 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4034 $chain->dump();
4035 echo PHP_EOL;
4036 }
4037
4038 echo PHP_EOL.PHP_EOL;
4039
4040 echo 'size of having_chains: '.count($this->having_chains);
4041 echo PHP_EOL;
4042 foreach ($this->having_chains as $num => $chain)
4043 {
4044 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4045 $chain->dump();
4046 echo PHP_EOL;
4047 }
4048
4049 echo PHP_EOL.PHP_EOL;
4050
4051 echo 'size of filter_chains: '.count($this->filter_chains);
4052 echo PHP_EOL;
4053 foreach ($this->filter_chains as $num => $chain)
4054 {
4055 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4056 $chain->dump();
4057 echo PHP_EOL;
4058 }
4059
4060 echo PHP_EOL.PHP_EOL;
4061
4062 echo 'size of select_expr_chains: '.count($this->select_expr_chains);
4063 echo PHP_EOL;
4064 foreach ($this->select_expr_chains as $num => $chain)
4065 {
4066 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4067 $chain->dump();
4068 echo PHP_EOL;
4069 }
4070
4071 echo PHP_EOL.PHP_EOL;
4072
4073 echo 'size of hidden_chains: '.count($this->hidden_chains);
4074 echo PHP_EOL;
4075 foreach ($this->hidden_chains as $num => $chain)
4076 {
4077 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4078 $chain->dump();
4079 echo PHP_EOL;
4080 }
4081
4082 echo PHP_EOL.PHP_EOL;
4083
4084 echo 'size of global_chains: '.count($this->global_chains);
4085 echo PHP_EOL;
4086 foreach ($this->global_chains as $num => $chain)
4087 {
4088 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4089 $chain->dump();
4090 echo PHP_EOL;
4091 }
4092
4093 echo PHP_EOL.PHP_EOL;
4094
4095 var_dump($this->join_map);
4096
4097 echo '</pre>';
4098 }
4099}
return select
Определения access_edit.php:440
$connection
Определения actionsdefinitions.php:38
$count
Определения admin_tab.php:4
static getInstance()
Определения application.php:98
static getInstance($entityName)
Определения entity.php:104
getAlias()
Определения chain.php:180
setCustomAlias($alias)
Определения chain.php:195
getDefinition($elementsSlice=0)
Определения chain.php:153
getLastElement()
Определения chain.php:96
static relayQuery(\Bitrix\Main\Data\Connection $connection, Query $query)
Определения nosqlprimaryselector.php:121
static checkQuery(\Bitrix\Main\Data\Connection $connection, Query $query)
Определения nosqlprimaryselector.php:25
setCustomBaseTableAlias($alias)
Определения query.php:889
hasDistinct()
Определения query.php:3340
static isFieldPrivate($field)
Определения query.php:806
getEntity()
Определения query.php:3897
$forcedObjectPrimaryFields
Определения query.php:173
setFilter(array $filter)
Определения query.php:381
setOrder($order)
Определения query.php:474
$uniqueAliasCounter
Определения query.php:232
quoteTableSource($source)
Определения query.php:3746
setDistinct($distinct=true)
Определения query.php:3333
checkFilterAggregation($filter)
Определения query.php:1737
$query_build_parts
Определения query.php:182
divideFilterHandler()
Определения query.php:1683
registerChain($section, Chain $chain, $opt_key=null)
Определения query.php:3440
disableDataDoubling()
Определения query.php:712
$join_registry
Определения query.php:211
divideFilter()
Определения query.php:1615
$having_expr_chains
Определения query.php:166
$hidden_chains
Определения query.php:167
getOrderChains()
Определения query.php:3839
checkForPrivateFields()
Определения query.php:763
disablePrivateFields()
Определения query.php:748
$unionHandler
Определения query.php:214
static filter()
Определения query.php:906
buildQuery($forceObjectPrimary=true)
Определения query.php:2466
setCacheTtl($ttl)
Определения query.php:3982
getFilter()
Определения query.php:370
$group_chains
Определения query.php:150
addToGroupChain($definition)
Определения query.php:1874
$global_chains
Определения query.php:179
buildWhere()
Определения query.php:2258
$whereHandler
Определения query.php:140
setFilterHandlerChains(Filter $where, $section='filter')
Определения query.php:1475
static $expressionHelper
Определения query.php:185
addToOrderChain($definition)
Определения query.php:1890
fetch(\Bitrix\Main\Text\Converter $converter=null)
Определения query.php:1001
$private_fields_on
Определения query.php:199
isPrivateFieldsEnabled()
Определения query.php:758
nullEqualityCallback($field, $operation, $value)
Определения query.php:3529
getHiddenChains()
Определения query.php:3815
getReplacedAliases()
Определения query.php:3972
getFilterChains()
Определения query.php:3831
getGroup()
Определения query.php:421
getOrder()
Определения query.php:457
dataDoublingCallback($field, $operation, $value)
Определения query.php:3534
getUnionHandler()
Определения query.php:3430
addOrder($definition, $order='ASC')
Определения query.php:508
registerRuntimeField($name, $fieldInfo=null)
Определения query.php:836
buildOrder()
Определения query.php:2408
fetchDataModificationCallback(&$data)
Определения query.php:3667
setUnionOffset($offset)
Определения query.php:688
$selectFetchModifiers
Определения query.php:235
isFetchModificationRequired()
Определения query.php:3684
addFilter($key, $value)
Определения query.php:394
$data_doubling_off
Определения query.php:192
setUnionOrder($order)
Определения query.php:642
$select_expr_chains
Определения query.php:165
$where_chains
Определения query.php:158
addGroup($group)
Определения query.php:446
enablePrivateFields()
Определения query.php:736
setSelect(array $select)
Определения query.php:338
setFilterChains(&$filter, $section='filter')
Определения query.php:1358
getJoinMap()
Определения query.php:3868
addSelect($definition, $alias='')
Определения query.php:351
setOffset($offset)
Определения query.php:577
$select_chains
Определения query.php:149
getSelect()
Определения query.php:327
$having_chains
Определения query.php:159
$replaced_aliases
Определения query.php:226
buildHaving()
Определения query.php:2377
unionAll()
Определения query.php:622
booleanStrongEqualityCallback($field, $operation, $value)
Определения query.php:3523
getHavingChains()
Определения query.php:3823
$filterHandler
Определения query.php:137
static expr($alias=null)
Определения query.php:919
$order_chains
Определения query.php:151
$havingHandler
Определения query.php:143
getWhereChains()
Определения query.php:3855
$table_alias_postfix
Определения query.php:202
$filter_chains
Определения query.php:157
prepareJoinFilterReference(Filter $reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference, $firstCall=true)
Определения query.php:3013
addUnionOrder($definition, $order='ASC')
Определения query.php:660
getFilterHandler()
Определения query.php:411
static $last_query
Определения query.php:223
setUnionLimit($limit)
Определения query.php:674
static buildFilterSql(Entity $entity, $filter)
Определения query.php:3913
getChains()
Определения query.php:3799
hasBackReference()
Определения query.php:3778
queryCountTotal()
Определения query.php:3634
fetchObject()
Определения query.php:1028
getRuntimeChains()
Определения query.php:3863
$is_executing
Определения query.php:220
hasAggregation()
Определения query.php:3326
getUniqueAlias()
Определения query.php:3518
getTableAliasPostfix()
Определения query.php:877
buildSelect()
Определения query.php:2171
buildGroup()
Определения query.php:2289
__call($method, $arguments)
Определения query.php:280
$replaced_taliases
Определения query.php:229
static getLastQuery()
Определения query.php:3892
setTableAliasPostfix($postfix)
Определения query.php:871
getSelectChains()
Определения query.php:3847
checkFilterHandlerAggregation(Filter $filter)
Определения query.php:1778
setGroup($group)
Определения query.php:432
getGroupChains()
Определения query.php:3807
replaceSelectAliases($query)
Определения query.php:3706
getLimit()
Определения query.php:544
enableDataDoubling()
Определения query.php:699
prepareJoinReference($reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference)
Определения query.php:2764
getQuery($forceObjectPrimary=false)
Определения query.php:3882
getInitAlias($withPostfix=true)
Определения query.php:3939
fetchCollection()
Определения query.php:1045
$custom_base_table_alias
Определения query.php:205
cacheJoins($mode)
Определения query.php:3993
getOffset()
Определения query.php:566
fetchAll(\Bitrix\Main\Text\Converter $converter=null)
Определения query.php:1015
setLimit($limit)
Определения query.php:555
getRegisteredChain($key, $force_create=false)
Определения query.php:3500
rewriteDataDoubling(Filter $filter, $section)
Определения query.php:1829
getJoinCswFields($reference)
Определения query.php:3238
countTotal($count=null)
Определения query.php:583
$runtime_chains
Определения query.php:176
__construct($source)
Определения query.php:247
buildJoin()
Определения query.php:2218
static getOperationByCode($code)
Определения sqlwhere.php:265
$data['IS_AVAILABLE']
Определения .description.php:13
$new
Определения file_edit.php:48
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
$query
Определения get_search.php:11
$entity
$select
Определения iblock_catalog_list.php:194
$filter
Определения iblock_catalog_list.php:54
$name
Определения menu_edit.php:35
Определения arrayresult.php:2
Определения ufield.php:9
Определения chain.php:3
Определения base32.php:2
$order
Определения payment.php:8
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
if(empty($signedUserToken)) $key
Определения quickway.php:257
if(empty($decryptedData)) $storage
Определения quickway.php:270
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"
Определения waybill.php:936
$method
Определения index.php:27
$matches
Определения index.php:22
$k
Определения template_pdf.php:567
foreach($arTemplatesList as $templ) if(mb_strpos($templ["NAME"] $def
Определения template_copy.php:264
$fields
Определения yandex_run.php:501