Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
query.php
1<?php
2
4
20
117class Query
118{
120 protected $entity;
121
122 protected
123 $select = array(),
124 $group = array(),
125 $order = array(),
126 $limit = null,
127 $offset = null,
129
130 // deprecated array filter format
131 protected
132 $filter = array(),
133 $where = array(),
134 $having = array();
135
137 protected $filterHandler;
138
140 protected $whereHandler;
141
143 protected $havingHandler;
144
148 protected // all chain storages keying by alias
149 $select_chains = array(),
150 $group_chains = array(),
151 $order_chains = array();
152
156 protected
157 $filter_chains = array(),
158 $where_chains = array(),
159 $having_chains = array();
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
235 protected $selectFetchModifiers = array();
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 (substr($method, 0, 6) === 'having')
284 {
285 $method = str_replace('having', 'where', $method);
286 }
287
288 if (substr($method, 0, 5) === '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 (substr($method, 0, 4) === '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 {
480 $order = array($order);
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 $order = strtoupper($order);
511
512 if (!in_array($order, array('ASC', 'DESC'), true))
513 {
514 throw new Main\ArgumentException(sprintf('Invalid order "%s"', $order));
515 }
516
517 $connection = $this->entity->getConnection();
518 $helper = $connection->getSqlHelper();
519
520 if ($order == 'ASC')
521 {
522 $order = $helper->getAscendingOrder();
523 }
524 else
525 {
526 $order = $helper->getDescendingOrder();
527 }
528
529 $this->order[$definition] = $order;
530
531 return $this;
532 }
533
539 public function getLimit()
540 {
541 return $this->limit;
542 }
543
550 public function setLimit($limit)
551 {
552 $this->limit = $limit;
553 return $this;
554 }
555
561 public function getOffset()
562 {
563 return $this->offset;
564 }
565
572 public function setOffset($offset)
573 {
574 $this->offset = $offset;
575 return $this;
576 }
577
578 public function countTotal($count = null)
579 {
580 if ($count === null)
581 {
582 return $this->countTotal;
583 }
584 else
585 {
586 $this->countTotal = (bool) $count;
587 return $this;
588 }
589 }
590
599 public function union()
600 {
601 foreach (func_get_args() as $arg)
602 {
603 $this->getUnionHandler()->addQuery(new UnionCondition($arg, false));
604 }
605
606 return $this;
607 }
608
617 public function unionAll()
618 {
619 foreach (func_get_args() as $arg)
620 {
621 $this->getUnionHandler()->addQuery(new UnionCondition($arg, true));
622 }
623
624 return $this;
625 }
626
637 public function setUnionOrder($order)
638 {
639 $this->getUnionHandler()->setOrder($order);
640 return $this;
641 }
642
655 public function addUnionOrder($definition, $order = 'ASC')
656 {
657 $this->getUnionHandler()->addOrder($definition, $order);
658 return $this;
659 }
660
669 public function setUnionLimit($limit)
670 {
671 $this->getUnionHandler()->setLimit($limit);
672 return $this;
673 }
674
683 public function setUnionOffset($offset)
684 {
685 $this->getUnionHandler()->setOffset($offset);
686 return $this;
687 }
688
694 public function enableDataDoubling()
695 {
696 $this->data_doubling_off = false;
697
698 return $this;
699 }
700
707 public function disableDataDoubling()
708 {
709 if (count($this->entity->getPrimaryArray()) !== 1)
710 {
711 // mssql doesn't support constructions WHERE (col1, col2) IN (SELECT col1, col2 FROM SomeOtherTable)
712 /* @see http://connect.microsoft.com/SQLServer/feedback/details/299231/add-support-for-ansi-standard-row-value-constructors */
713 trigger_error(sprintf(
714 'Disabling data doubling available for Entities with 1 primary field only. Number of primaries of your entity `%s` is %d.',
715 $this->entity->getFullName(), count($this->entity->getPrimaryArray())
716 ), E_USER_WARNING);
717 }
718 else
719 {
720 $this->data_doubling_off = true;
721 }
722
723 return $this;
724 }
725
731 public function enablePrivateFields()
732 {
733 $this->private_fields_on = true;
734
735 return $this;
736 }
737
743 public function disablePrivateFields()
744 {
745 $this->private_fields_on = false;
746
747 return $this;
748 }
749
753 public function isPrivateFieldsEnabled()
754 {
755 return $this->private_fields_on;
756 }
757
758 protected function checkForPrivateFields()
759 {
760 // check in filter
761 foreach ($this->filter_chains as $chain)
762 {
763 if (static::isFieldPrivate($chain->getLastElement()->getValue()))
764 {
765 $columnField = $chain->getLastElement()->getValue();
766
767 throw new SystemException(sprintf(
768 'Private field %s.%s is restricted in filter',
769 $columnField->getEntity()->getDataClass(),
770 $columnField->getName()
771 ));
772 }
773 }
774
775 // check in general
776 if ($this->private_fields_on !== true)
777 {
778 foreach ($this->global_chains as $chain)
779 {
780 if (static::isFieldPrivate($chain->getLastElement()->getValue()))
781 {
782 $columnField = $chain->getLastElement()->getValue();
783
784 throw new SystemException(sprintf(
785 'Private field %s.%s is restricted in query, use Query::enablePrivateFields() to allow it',
786 $columnField->getEntity()->getDataClass(),
787 $columnField->getName()
788 ));
789 }
790 }
791 }
792 }
793
801 public static function isFieldPrivate($field)
802 {
803 if ($field instanceof ScalarField)
804 {
805 return $field->isPrivate();
806 }
807 elseif ($field instanceof ExpressionField)
808 {
809 foreach ($field->getBuildFromChains() as $chain)
810 {
811 if (static::isFieldPrivate($chain->getLastElement()->getValue()))
812 {
813 return true;
814 }
815 }
816 }
817
818 return false;
819 }
820
831 public function registerRuntimeField($name, $fieldInfo = null)
832 {
833 if ($name instanceof Field && $fieldInfo === null)
834 {
835 // short call for Field objects
836 $fieldInfo = $name;
837 $name = $fieldInfo->getName();
838 }
839 elseif ((empty($name) || is_numeric($name)) && $fieldInfo instanceof Field)
840 {
841 $name = $fieldInfo->getName();
842 }
843
844 // clone field as long as Field object could be initialized only once
845 // there is no need to initialize original object
846 if ($fieldInfo instanceof Field)
847 {
848 $fieldInfo = clone $fieldInfo;
849 }
850
851 // attach field to the entity
852 $this->entity->addField($fieldInfo, $name);
853
854 // force chain creation for further needs
855 $chain = $this->getRegisteredChain($name, true);
856 $this->registerChain('runtime', $chain);
857
858 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
859 {
860 $this->collectExprChains($chain, array('hidden'));
861 }
862
863 return $this;
864 }
865
866 public function setTableAliasPostfix($postfix)
867 {
868 $this->table_alias_postfix = $postfix;
869 return $this;
870 }
871
872 public function getTableAliasPostfix()
873 {
874 return $this->table_alias_postfix;
875 }
876
884 public function setCustomBaseTableAlias($alias)
885 {
886 $this->custom_base_table_alias = $alias;
887 return $this;
888 }
889
901 public static function filter()
902 {
903 return new Filter;
904 }
905
912 public static function expr()
913 {
914 if (static::$expressionHelper === null)
915 {
916 static::$expressionHelper = new Expression;
917 }
918
919 return static::$expressionHelper;
920 }
921
929 public function exec()
930 {
931 $this->is_executing = true;
932
933 $query = $this->buildQuery();
934
935 $cacheId = "";
936 $ttl = 0;
937 $result = null;
938
939 if($this->cacheTtl > 0 && (empty($this->join_map) || $this->cacheJoins == true))
940 {
941 $ttl = $this->entity->getCacheTtl($this->cacheTtl);
942 }
943
944 if($ttl > 0)
945 {
946 $cacheId = md5($query);
947 $result = $this->entity->readFromCache($ttl, $cacheId, $this->countTotal);
948 }
949
950 if($result === null)
951 {
952 $result = $this->query($query);
953
954 if($ttl > 0)
955 {
956 $result = $this->entity->writeToCache($result, $cacheId, $this->countTotal);
957 }
958 }
959
960 $this->is_executing = false;
961
962 $queryResult = new Result($this, $result);
963
964 if (!empty($this->forcedObjectPrimaryFields))
965 {
966 $queryResult->setHiddenObjectFields($this->forcedObjectPrimaryFields);
967 }
968
969 return $queryResult;
970 }
971
981 public function fetch(\Bitrix\Main\Text\Converter $converter = null)
982 {
983 return $this->exec()->fetch($converter);
984 }
985
995 public function fetchAll(\Bitrix\Main\Text\Converter $converter = null)
996 {
997 return $this->exec()->fetchAll($converter);
998 }
999
1008 public function fetchObject()
1009 {
1010 return $this->exec()->fetchObject();
1011 }
1012
1020 public function fetchCollection()
1021 {
1022 return $this->exec()->fetchCollection();
1023 }
1024
1025 protected function ensurePrimarySelect()
1026 {
1027 // no auto primary for queries with group
1028 // it may change the result
1029 if ($this->hasAggregation() || $this->hasDistinct())
1030 {
1031 return;
1032 }
1033
1034 $entities = [[$this->entity, '']];
1035
1036 foreach ($this->join_map as $join)
1037 {
1038 $entities[] = [$join['entity'], $join];
1039 }
1040
1041 // check for primaries in select
1042 foreach ($entities as list($entity, $join))
1043 {
1045 foreach ($entity->getPrimaryArray() as $primary)
1046 {
1047 if (!empty($entity->getField($primary)->hasParameter('auto_generated')))
1048 {
1049 continue;
1050 }
1051
1052 $needDefinition = !empty($join['definition']) ? $join['definition'].'.'.$primary : $primary;
1053
1054 $chain = $this->getRegisteredChain($needDefinition, true);
1055
1056 if (empty($this->select_chains[$chain->getAlias()]))
1057 {
1058 // set uniq alias
1059 $alias = $this->getUniqueAlias();
1060 $chain->setCustomAlias($alias);
1061
1062 $this->registerChain('select', $chain);
1063
1064 // remember to delete alias from array result
1065 $this->forcedObjectPrimaryFields[] = $alias;
1066
1067 // set join alias
1068 !empty($join)
1069 ? $chain->getLastElement()->setParameter('talias', $join['alias'])
1070 : $chain->getLastElement()->setParameter('talias', $this->getInitAlias());
1071 }
1072 }
1073 }
1074 }
1075
1084 protected function addToSelectChain($definition, $alias = null)
1085 {
1086 if ($definition instanceof ExpressionField)
1087 {
1088 if (empty($alias))
1089 {
1090 $alias = $definition->getName();
1091 }
1092
1093 $this->registerRuntimeField($alias, $definition);
1094 $chain = $this->getRegisteredChain($alias);
1095
1096 // add
1097 $this->registerChain('select', $chain);
1098
1099 // recursively collect all "build_from" fields
1100 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1101 {
1102 $this->collectExprChains($chain, array('hidden', 'select_expr'));
1103 }
1104 }
1105 elseif (is_array($definition))
1106 {
1107 // it is runtime field
1108 // now they are @deprecated in here
1109 throw new Main\ArgumentException(
1110 'Expression as an array in `select` section is no more supported due to security reason.'
1111 .' Please use `runtime` parameter, or Query->registerRuntimeField method, or pass ExpressionField object instead of array.'
1112 );
1113 }
1114 else
1115 {
1116 // localize definition (get last field segment e.g. NAME from REF1.REF2.NAME)
1117 $localDefinitionPos = strrpos($definition, '.');
1118
1119 if ($localDefinitionPos !== false)
1120 {
1121 $localDefinition = substr($definition, $localDefinitionPos + 1);
1122 $localEntityDef = substr($definition, 0, $localDefinitionPos);
1123 $localChain = Chain::getChainByDefinition($this->entity, $localEntityDef.'.*');
1124 $lastElemValue = $localChain->getLastElement()->getValue();
1125
1126 if ($lastElemValue instanceof Reference)
1127 {
1128 $localEntity = $lastElemValue->getRefEntity();
1129 }
1130 elseif (is_array($lastElemValue))
1131 {
1132 list($localEntity, ) = $lastElemValue;
1133 }
1134 else
1135 {
1136 $localEntity = $lastElemValue;
1137 }
1138 }
1139 else
1140 {
1141 $localDefinition = $definition;
1142 $localEntityDef = "";
1143 $dataClass = $this->entity->getDataClass();
1144 $localEntity = $dataClass::getEntity();
1145 }
1146
1147 // if there is a shell pattern in final segment, run recursively
1148 if ((strlen($localDefinition) > 1 && strpos($localDefinition, '*') !== false)
1149 || strpos($localDefinition, '?') !== false
1150 )
1151 {
1152 // get fields by pattern
1153 foreach ($localEntity->getFields() as $field)
1154 {
1155 if (
1156 ($field instanceof ScalarField || $field instanceof ExpressionField)
1157 && fnmatch($localDefinition, $field->getName())
1158 )
1159 {
1160 // skip private fields
1161 if ($field instanceof ScalarField && $field->isPrivate())
1162 {
1163 continue;
1164 }
1165
1166 // skip uf utm single
1167 if (
1168 substr($field->getName(), 0, 3) == 'UF_' && substr($field->getName(), -7) == '_SINGLE'
1169 && $localEntity->hasField(substr($field->getName(), 0, -7))
1170 )
1171 {
1172 continue;
1173 }
1174
1175
1176 // build alias
1177 $customAlias = null;
1178
1179 if ($alias !== null)
1180 {
1181 // put alias as a prefix
1182 $customAlias = $alias.$field->getName();
1183 }
1184
1185 // build definition
1186 $fieldDefinition = $field->getName();
1187
1188 if (!empty($localEntityDef))
1189 {
1190 $fieldDefinition = $localEntityDef.'.'.$fieldDefinition;
1191 }
1192
1193 $this->addToSelectChain($fieldDefinition, $customAlias);
1194 }
1195 }
1196
1197 return $this;
1198 }
1199
1200 // there is normal scalar field, or Reference, or Entity (all fields of)
1201 $chain = $this->getRegisteredChain($definition, true);
1202
1203 if ($alias !== null)
1204 {
1205 // custom alias
1206 $chain = clone $chain;
1207 $chain->setCustomAlias($alias);
1208 }
1209
1210 $last_elem = $chain->getLastElement();
1211
1212 // fill if element is not scalar
1214 $expand_entity = null;
1215
1216 if ($last_elem->getValue() instanceof Reference)
1217 {
1218 $expand_entity = $last_elem->getValue()->getRefEntity();
1219 }
1220 elseif (is_array($last_elem->getValue()))
1221 {
1222 list($expand_entity, ) = $last_elem->getValue();
1223 }
1224 elseif ($last_elem->getValue() instanceof Entity)
1225 {
1226 $expand_entity = $last_elem->getValue();
1227 }
1228 elseif ($last_elem->getValue() instanceof OneToMany)
1229 {
1230 $expand_entity = $last_elem->getValue()->getRefEntity();
1231 }
1232 elseif ($last_elem->getValue() instanceof ManyToMany)
1233 {
1234 $expand_entity = $last_elem->getValue()->getRefEntity();
1235 }
1236
1237 if (!$expand_entity && $alias !== null)
1238 {
1239 // we have a single field, let's check its custom alias
1240 if (
1241 $this->entity->hasField($alias)
1242 && (
1243 // if it's not the same field
1244 $this->entity->getFullName() !== $last_elem->getValue()->getEntity()->getFullName()
1245 ||
1246 $last_elem->getValue()->getName() !== $alias
1247 )
1248 )
1249 {
1250 // deny aliases eq. existing fields
1251 throw new Main\ArgumentException(sprintf(
1252 'Alias "%s" matches already existing field "%s" of initial entity "%s". '.
1253 'Please choose another name for alias.',
1254 $alias, $alias, $this->entity->getFullName()
1255 ));
1256 }
1257 }
1258
1259 if ($expand_entity)
1260 {
1261 // add all fields of entity
1262 foreach ($expand_entity->getFields() as $exp_field)
1263 {
1264 // except for references and expressions
1265 if ($exp_field instanceof ScalarField)
1266 {
1267 // skip private fields
1268 if ($exp_field->isPrivate())
1269 {
1270 continue;
1271 }
1272
1273 $exp_chain = clone $chain;
1274 $exp_chain->addElement(new ChainElement(
1275 $exp_field
1276 ));
1277
1278 // custom alias
1279 if ($alias !== null)
1280 {
1281 $fieldAlias = $alias . $exp_field->getName();
1282
1283 // deny aliases eq. existing fields
1284 if ($this->entity->hasField($fieldAlias))
1285 {
1286 throw new Main\ArgumentException(sprintf(
1287 'Alias "%s" + field "%s" match already existing field "%s" of initial entity "%s". '.
1288 'Please choose another name for alias.',
1289 $alias, $exp_field->getName(), $fieldAlias, $this->entity->getFullName()
1290 ));
1291 }
1292
1293 $exp_chain->setCustomAlias($fieldAlias);
1294 }
1295
1296 // add
1297 $this->registerChain('select', $exp_chain);
1298 }
1299 }
1300 }
1301 else
1302 {
1303 // scalar field that defined in entity
1304 $this->registerChain('select', $chain);
1305
1306 // it would be nice here to register field as a runtime when it has custom alias
1307 // it will make possible to use aliased fields as a native init entity fields
1308 // e.g. in expressions or in data_doubling=off filter
1309
1310 // collect buildFrom fields (recursively)
1311 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1312 {
1313 $this->collectExprChains($chain, array('hidden', 'select_expr'));
1314 }
1315 }
1316 }
1317
1318 return $this;
1319 }
1320
1328 public function setFilterChains(&$filter, $section = 'filter')
1329 {
1330 foreach ($filter as $filter_def => &$filter_match)
1331 {
1332 if ($filter_def === 'LOGIC')
1333 {
1334 continue;
1335 }
1336
1337 if (!is_numeric($filter_def))
1338 {
1339 $sqlWhere = new \CSQLWhere();
1340 $csw_result = $sqlWhere->makeOperation($filter_def);
1341 list($definition, ) = array_values($csw_result);
1342
1343 // do not register it in global chain registry - get it in a smuggled way
1344 // - we will do the registration later after UF rewriting and data doubling checking
1345 $chain = $this->getRegisteredChain($definition);
1346
1347 if (!$chain)
1348 {
1349 // try to find it in filter chains if it is 2nd call of method (when dividing filter for where/having)
1350 // and chain is still not registered in global (e.g. when forcesDataDoublingOff)
1351 $chain = $this->filter_chains[$definition] ?? Chain::getChainByDefinition($this->entity, $definition);
1352 }
1353
1354 // dirty hack for UF multiple fields: replace text UF_SMTH by UF_SMTH_SINGLE
1355 $dstField = $chain->getLastElement()->getValue();
1356 $dstEntity = $dstField->getEntity();
1357
1358 if ($dstField instanceof ExpressionField && count($dstField->getBuildFromChains()) == 1)
1359 {
1360 // hold entity, but get real closing field
1361 $dstBuildFromChains = $dstField->getBuildFromChains();
1362
1364 $firstChain = $dstBuildFromChains[0];
1365 $dstField = $firstChain->getLastElement()->getValue();
1366 }
1367
1368 // check for base linking
1369 if (($dstField instanceof TextField || $dstField instanceof ArrayField)
1370 && $dstEntity->hasField($dstField->getName().'_SINGLE'))
1371 {
1372 $utmLinkField = $dstEntity->getField($dstField->getName().'_SINGLE');
1373
1374 if ($utmLinkField instanceof ExpressionField)
1375 {
1376 $buildFromChains = $utmLinkField->getBuildFromChains();
1377
1378 // check for back-reference
1379 if (count($buildFromChains) == 1 && $buildFromChains[0]->hasBackReference())
1380 {
1381 $endField = $buildFromChains[0]->getLastElement()->getValue();
1382
1383 // and final check for entity name
1384 if(strpos($endField->getEntity()->getName(), 'Utm'))
1385 {
1386 $expressionChain = clone $chain;
1387 $expressionChain->removeLastElement();
1388 $expressionChain->addElement(new ChainElement(clone $utmLinkField));
1389 $expressionChain->forceDataDoublingOff();
1390
1391 $chain = $expressionChain;
1392
1393 // rewrite filter definition
1394 unset($filter[$filter_def]);
1395 $filter[$filter_def.'_SINGLE'] = $filter_match;
1396 $definition .= '_SINGLE';
1397 }
1398 }
1399 }
1400 }
1401
1402 // continue
1403 $registerChain = true;
1404
1405 // if data doubling disabled and it is back-reference - do not register, it will be overwritten
1406 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1407 {
1408 $registerChain = false;
1409 }
1410
1411 if ($registerChain)
1412 {
1413 $this->registerChain($section, $chain, $definition);
1414
1415 // fill hidden select
1416 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1417 {
1418 $this->collectExprChains($chain);
1419 }
1420 }
1421 else
1422 {
1423 // hide from global registry to avoid "join table"
1424 // but we still need it in filter chains
1425 $this->filter_chains[$chain->getAlias()] = $chain;
1426 $this->filter_chains[$definition] = $chain;
1427
1428 // and we will need primary chain in filter later when overwriting data-doubling
1429 $this->getRegisteredChain($this->entity->getPrimary(), true);
1430 }
1431 }
1432 elseif (is_array($filter_match))
1433 {
1434 $this->setFilterChains($filter_match, $section);
1435 }
1436 }
1437 }
1438
1446 public function setFilterHandlerChains(Filter $where, $section = 'filter')
1447 {
1448 foreach ($where->getConditions() as $condition)
1449 {
1450 if ($condition instanceof Filter)
1451 {
1452 // subfilter
1453 $this->setFilterHandlerChains($condition, $section);
1454 }
1455 else
1456 {
1457 $definition = $condition->getDefinition();
1458
1459 // check for runtime fields
1460 if ($definition instanceof Field)
1461 {
1462 // register runtime field
1463 $this->registerRuntimeField($definition);
1464
1465 // rewrite definition in filter - replace field with its name
1466 $definition = $definition->getName();
1467 $condition->setDefinition($definition);
1468 }
1469
1470 // check if it's a regular condition, not kind of boolean/exists expression
1471 if ($definition !== null)
1472 {
1473 // regular condition
1474 $chain = $this->getRegisteredChain($definition);
1475
1476 if (!$chain)
1477 {
1478 // try to find it in filter chains if it is 2nd call of method (when dividing filter for where/having)
1479 // and chain is still not registered in global (e.g. when forcesDataDoublingOff)
1480 $chain = $this->filter_chains[$definition] ?? Chain::getChainByDefinition($this->entity, $definition);
1481 }
1482
1483 // dirty hack for UF multiple fields: replace text UF_SMTH by UF_SMTH_SINGLE
1484 $dstField = $chain->getLastElement()->getValue();
1485 $dstEntity = $dstField->getEntity();
1486
1487 if ($dstField instanceof ExpressionField && count($dstField->getBuildFromChains()) == 1)
1488 {
1489 // hold entity, but get real closing field
1490 $dstBuildFromChains = $dstField->getBuildFromChains();
1491
1493 $firstChain = $dstBuildFromChains[0];
1494 $dstField = $firstChain->getLastElement()->getValue();
1495 }
1496
1497 // check for base linking
1498 if (($dstField instanceof TextField || $dstField instanceof ArrayField)
1499 && $dstEntity->hasField($dstField->getName().'_SINGLE'))
1500 {
1501 $utmLinkField = $dstEntity->getField($dstField->getName().'_SINGLE');
1502
1503 if ($utmLinkField instanceof ExpressionField)
1504 {
1505 $buildFromChains = $utmLinkField->getBuildFromChains();
1506
1507 // check for back-reference
1508 if (count($buildFromChains) == 1 && $buildFromChains[0]->hasBackReference())
1509 {
1510 $endField = $buildFromChains[0]->getLastElement()->getValue();
1511
1512 // and final check for entity name
1513 if(strpos($endField->getEntity()->getName(), 'Utm'))
1514 {
1515 $expressionChain = clone $chain;
1516 $expressionChain->removeLastElement();
1517 $expressionChain->addElement(new ChainElement(clone $utmLinkField));
1518 $expressionChain->forceDataDoublingOff();
1519
1520 $chain = $expressionChain;
1521
1522 // rewrite filter definition
1523 $definition .= '_SINGLE';
1524 $condition->setDefinition($definition);
1525 }
1526 }
1527 }
1528 }
1529
1530 // continue
1531 $registerChain = true;
1532
1533 // if data doubling disabled and it is back-reference - do not register, it will be overwritten
1534 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1535 {
1536 $registerChain = false;
1537 }
1538
1539 if ($registerChain)
1540 {
1541 $this->registerChain($section, $chain, $definition);
1542
1543 // fill hidden select
1544 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1545 {
1546 $this->collectExprChains($chain);
1547 }
1548 }
1549 else
1550 {
1551 // hide from global registry to avoid "join table"
1552 // but we still need it in filter chains
1553 $this->filter_chains[$chain->getAlias()] = $chain;
1554 $this->filter_chains[$definition] = $chain;
1555
1556 // and we will need primary chain in filter later when overwriting data-doubling
1557 $this->getRegisteredChain($this->entity->getPrimary(), true);
1558 }
1559 }
1560
1561 // when compare with column, put it in the chains too
1562 foreach ($condition->getAtomicValues() as $value)
1563 {
1564 if ($value instanceof ColumnExpression)
1565 {
1566 $valueDefinition = $value->getDefinition();
1567
1568 $chain = $this->filter_chains[$valueDefinition] ?? Chain::getChainByDefinition($this->entity, $valueDefinition);
1569
1570 $this->registerChain($section, $chain, $valueDefinition);
1571 }
1572
1573 // set connection to correct escaping in expressions
1574 if ($value instanceof Main\DB\SqlExpression)
1575 {
1576 $value->setConnection($this->entity->getConnection());
1577 }
1578 }
1579 }
1580 }
1581 }
1582
1587 protected function divideFilter()
1588 {
1589 // divide filter to where and having
1590
1591 $logic = $this->filter['LOGIC'] ?? 'AND';
1592
1593 if ($logic == 'OR')
1594 {
1595 // if has aggr then move all to having
1596 if ($this->checkFilterAggregation($this->filter))
1597 {
1598 $this->where = array();
1599 $this->where_chains = array();
1600
1601 $this->having = $this->filter;
1602 $this->having_chains = $this->filter_chains;
1603 }
1604 else
1605 {
1606 $this->where = $this->filter;
1607 $this->where_chains = $this->filter_chains;
1608
1609 $this->having = array();
1610 $this->having_chains = array();
1611 }
1612 }
1613 elseif ($logic == 'AND')
1614 {
1615 // we can separate root filters
1616 foreach ($this->filter as $k => $sub_filter)
1617 {
1618 if ($k === 'LOGIC')
1619 {
1620 $this->where[$k] = $sub_filter;
1621 $this->having[$k] = $sub_filter;
1622
1623 continue;
1624 }
1625
1626 $tmp_filter = array($k => $sub_filter);
1627
1628 if ($this->checkFilterAggregation($tmp_filter))
1629 {
1630 $this->having[$k] = $sub_filter;
1631 $this->setFilterChains($tmp_filter, 'having');
1632 }
1633 else
1634 {
1635 $this->where[$k] = $sub_filter;
1636 $this->setFilterChains($tmp_filter, 'where');
1637 }
1638 }
1639 }
1640
1641 // collect "build_from" fields from having
1642 foreach ($this->having_chains as $chain)
1643 {
1644 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1645 {
1646 $this->collectExprChains($chain, array('hidden', 'having_expr'));
1647 }
1648 }
1649 }
1650
1655 protected function divideFilterHandler()
1656 {
1657 $logic = $this->filterHandler->logic();
1658
1659 if ($logic == 'or')
1660 {
1661 // if has aggr then move all to having
1662 if ($this->checkFilterHandlerAggregation($this->filterHandler))
1663 {
1664 $this->havingHandler = $this->filterHandler;
1665 $this->having_chains = $this->filter_chains;
1666 }
1667 else
1668 {
1669 $this->whereHandler = $this->filterHandler;
1670 $this->where_chains = $this->filter_chains;
1671 }
1672 }
1673 elseif ($logic == 'and')
1674 {
1675 // we can separate root filters
1676 foreach ($this->filterHandler->getConditions() as $condition)
1677 {
1678 $tmpFilter = static::filter()->addCondition($condition);
1679
1680 if ($this->checkFilterHandlerAggregation($tmpFilter))
1681 {
1682 $this->havingHandler->addCondition($tmpFilter);
1683 $this->setFilterHandlerChains($tmpFilter, 'having');
1684 }
1685 else
1686 {
1687 $this->whereHandler->addCondition($condition);
1688 $this->setFilterHandlerChains($tmpFilter, 'where');
1689 }
1690 }
1691 }
1692
1693 // collect "build_from" fields from having
1694 foreach ($this->having_chains as $chain)
1695 {
1696 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1697 {
1698 $this->collectExprChains($chain, array('hidden', 'having_expr'));
1699 }
1700 }
1701 }
1702
1709 protected function checkFilterAggregation($filter)
1710 {
1711 foreach ($filter as $filter_def => $filter_match)
1712 {
1713 if ($filter_def === 'LOGIC')
1714 {
1715 continue;
1716 }
1717
1718 $is_having = false;
1719 if (!is_numeric($filter_def))
1720 {
1721 $sqlWhere = new \CSQLWhere();
1722 $csw_result = $sqlWhere->makeOperation($filter_def);
1723 list($definition, ) = array_values($csw_result);
1724
1725 $chain = $this->filter_chains[$definition];
1726 $last = $chain->getLastElement();
1727
1728 $is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
1729 }
1730 elseif (is_array($filter_match))
1731 {
1732 $is_having = $this->checkFilterAggregation($filter_match);
1733 }
1734
1735 if ($is_having)
1736 {
1737 return true;
1738 }
1739 }
1740
1741 return false;
1742 }
1743
1750 protected function checkFilterHandlerAggregation(Filter $filter)
1751 {
1752 foreach ($filter->getConditions() as $condition)
1753 {
1754 $is_having = false;
1755
1756 if ($condition instanceof Filter)
1757 {
1758 // subfilter
1759 $is_having = $this->checkFilterHandlerAggregation($condition);
1760 }
1761 else
1762 {
1763 // check if it is not a boolean/exists condition
1764 if ($condition->getDefinition() !== null)
1765 {
1766 // regular condition
1767 $chain = $this->filter_chains[$condition->getDefinition()];
1768 $last = $chain->getLastElement();
1769
1770 $is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
1771
1772 // check if value is a field and has aggregation
1773 if (!$is_having && $condition->getValue() instanceof ColumnExpression)
1774 {
1775 $chain = $this->filter_chains[$condition->getValue()->getDefinition()];
1776 $last = $chain->getLastElement();
1777
1778 $is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
1779
1780 // actually if it has happened, we need to add group by the first column
1781 }
1782 }
1783 }
1784
1785 if ($is_having)
1786 {
1787 return true;
1788 }
1789 }
1790
1791 return false;
1792 }
1793
1801 protected function rewriteDataDoubling(Filter $filter, $section)
1802 {
1803 foreach ($filter->getConditions() as $condition)
1804 {
1805 if ($condition instanceof Filter)
1806 {
1807 //subfilter
1808 $this->rewriteDataDoubling($condition, $section);
1809 }
1810 elseif ($condition->getDefinition() !== null)
1811 {
1812 // regular condition
1813 $chain = $this->filter_chains[$condition->getDefinition()];
1814
1815 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1816 {
1817 $primaryName = $this->entity->getPrimary();
1818 $uniquePostfix = '_TMP'.rand();
1819
1820 // build subquery
1821 $dataClass = $this->entity->getDataClass();
1822
1823 $subQuery = $dataClass::query()
1824 ->addSelect($primaryName)
1825 ->where(clone $condition)
1826 ->setTableAliasPostfix(strtolower($uniquePostfix));
1827
1828 // change condition
1829 $condition->setColumn($primaryName);
1830 $condition->setOperator('in');
1831 $condition->setValue($subQuery);
1832
1833 // register primary's chain
1834 $idChain = $this->getRegisteredChain($primaryName);
1835 $this->registerChain($section, $idChain, $primaryName);
1836 }
1837 }
1838 }
1839 }
1840
1846 protected function addToGroupChain($definition)
1847 {
1848 $chain = $this->getRegisteredChain($definition, true);
1849 $this->registerChain('group', $chain);
1850
1851 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1852 {
1853 $this->collectExprChains($chain);
1854 }
1855 }
1856
1862 protected function addToOrderChain($definition)
1863 {
1864 $chain = $this->getRegisteredChain($definition, true);
1865 $this->registerChain('order', $chain);
1866
1867 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
1868 {
1869 $this->collectExprChains($chain);
1870 }
1871 }
1872
1879 protected function buildJoinMap($chains = null)
1880 {
1881 $connection = $this->entity->getConnection();
1882 $helper = $connection->getSqlHelper();
1883
1884 $aliasLength = $helper->getAliasLength();
1885
1886 if (empty($chains))
1887 {
1888 $chains = $this->global_chains;
1889 }
1890
1891 foreach ($chains as $chain)
1892 {
1893 if ($chain->getLastElement()->getParameter('talias'))
1894 {
1895 // already been here
1896 continue;
1897 }
1898
1899 // in NO_DOUBLING mode skip 1:N relations that presented in filter only
1900 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
1901 {
1902 $alias = $chain->getAlias();
1903
1904 if (isset($this->filter_chains[$alias])
1905 && !isset($this->select_chains[$alias]) && !isset($this->select_expr_chains[$alias])
1906 && !isset($this->group_chains[$alias]) && !isset($this->order_chains[$alias])
1907 )
1908 {
1909 continue;
1910 }
1911 }
1912
1913 $prev_alias = $this->getInitAlias(false);
1914
1915 $map_key = '';
1916
1921 $elements = array_slice($chain->getAllElements(), 1);
1922
1923 $currentDefinition = array();
1924
1925 foreach ($elements as $element)
1926 {
1927 $table_alias = null;
1928
1934 if ($element->getValue() instanceof Reference)
1935 {
1936 // ref to another entity
1937 $ref_field = $element->getValue();
1938 $dst_entity = $ref_field->getRefEntity();
1939 $joinType = $ref_field->getJoinType();
1940 }
1941 elseif (is_array($element->getValue()))
1942 {
1943 // link from another entity to this
1944 list($dst_entity, $ref_field) = $element->getValue();
1945 $joinType = $ref_field->getJoinType();
1946 }
1947 elseif ($element->getValue() instanceof OneToMany)
1948 {
1949 // the same as back reference
1950 $dst_entity = $element->getValue()->getRefEntity();
1951 $ref_field = $element->getValue()->getRefField();
1952 $joinType = $element->getValue()->getJoinType() ?: $ref_field->getJoinType();
1953 }
1954 elseif ($element->getValue() instanceof ManyToMany)
1955 {
1956 $mtm = $element->getValue();
1957
1958 // join mediator and remote entities in hidden mode
1959 // first, make new chain, remove everything after this mtm and remove mtm itself
1960 $tmpChain = clone $chain;
1961 $mtmDefinition = join('.', $currentDefinition);
1962
1963 while ($tmpChain->getDefinition() != $mtmDefinition)
1964 {
1965 $tmpChain->removeLastElement();
1966 }
1967
1968 // then add backReference to mediator - mediator entity and local reference
1969 $tmpChain->addElement(new ChainElement([
1970 $mtm->getMediatorEntity(), $mtm->getLocalReference()
1971 ]));
1972
1973 // then add reference from mediator to remote entity
1974 $tmpChain->addElement(new ChainElement($mtm->getRemoteReference()));
1975
1976 // now join this chain
1977 $this->registerChain('global', $tmpChain);
1978 $this->buildJoinMap([$tmpChain]);
1979
1980 // and finally remember table alias for mtm element
1981 $prev_alias = $tmpChain->getLastElement()->getParameter('talias');
1982 $element->setParameter('talias', $prev_alias);
1983
1984 // skip any standard actions, continue with next element
1985 continue;
1986 }
1987 else
1988 {
1989 // scalar field
1990 // if it's a field of the init entity, use getInitAlias to use 'base' alias
1991 if ($prev_alias === $this->getInitAlias(false))
1992 {
1993 $element->setParameter('talias', $this->getInitAlias());
1994 }
1995 else
1996 {
1997 $element->setParameter('talias', $prev_alias.$this->table_alias_postfix);
1998 }
1999
2000 continue;
2001 }
2002
2003 // mapping
2004 if (empty($map_key))
2005 {
2006 $map_key = join('.', $currentDefinition);
2007 }
2008
2009 $map_key .= '/' . $ref_field->getName() . '/' . $dst_entity->getName();
2010
2011 $currentDefinition[] = $element->getDefinitionFragment();
2012
2013 if (isset($this->join_registry[$map_key]))
2014 {
2015 // already connected
2016 $table_alias = $this->join_registry[$map_key];
2017 }
2018 else
2019 {
2020 // prepare reference
2021 $reference = $ref_field->getReference();
2022
2023 if ($element->getValue() instanceof Reference)
2024 {
2025 // ref to another entity
2026 if (is_null($table_alias))
2027 {
2028 $table_alias = $prev_alias.'_'.strtolower($ref_field->getName());
2029
2030 if (strlen($table_alias.$this->table_alias_postfix) > $aliasLength)
2031 {
2032 $old_table_alias = $table_alias;
2033 $table_alias = 'TALIAS_' . (count($this->replaced_taliases) + 1);
2034 $this->replaced_taliases[$table_alias] = $old_table_alias;
2035 }
2036 }
2037
2038 $alias_this = $prev_alias;
2039 $alias_ref = $table_alias;
2040
2041 $isBackReference = false;
2042
2043 $definition_this = join('.', array_slice($currentDefinition, 0, -1));
2044 $definition_ref = join('.', $currentDefinition);
2045 $definition_join = $definition_ref;
2046 }
2047 elseif (is_array($element->getValue()) || $element->getValue() instanceof OneToMany)
2048 {
2049 if (is_null($table_alias))
2050 {
2051 $table_alias = StringHelper::camel2snake($dst_entity->getName()).'_'.strtolower($ref_field->getName());
2052 $table_alias = $prev_alias.'_'.$table_alias;
2053
2054 if (strlen($table_alias.$this->table_alias_postfix) > $aliasLength)
2055 {
2056 $old_table_alias = $table_alias;
2057 $table_alias = 'TALIAS_' . (count($this->replaced_taliases) + 1);
2058 $this->replaced_taliases[$table_alias] = $old_table_alias;
2059 }
2060 }
2061
2062 $alias_this = $table_alias;
2063 $alias_ref = $prev_alias;
2064
2065 $isBackReference = true;
2066
2067 $definition_this = join('.', $currentDefinition);
2068 $definition_ref = join('.', array_slice($currentDefinition, 0, -1));
2069 $definition_join = $definition_this;
2070 }
2071 else
2072 {
2073 throw new Main\SystemException(sprintf('Unknown reference element `%s`', $element->getValue()));
2074 }
2075
2076 // replace this. and ref. to real definition
2077 if ($reference instanceof Filter)
2078 {
2079 $csw_reference = $this->prepareJoinFilterReference(
2080 $reference,
2081 $alias_this.$this->table_alias_postfix,
2082 $alias_ref.$this->table_alias_postfix,
2083 $definition_this,
2084 $definition_ref,
2085 $isBackReference
2086 );
2087 }
2088 else
2089 {
2090 $csw_reference = $this->prepareJoinReference(
2091 $reference,
2092 $alias_this.$this->table_alias_postfix,
2093 $alias_ref.$this->table_alias_postfix,
2094 $definition_this,
2095 $definition_ref,
2096 $isBackReference
2097 );
2098 }
2099
2100 // double check after recursive call in prepareJoinReference
2101 if (!isset($this->join_registry[$map_key]))
2102 {
2103 $join = array(
2104 'type' => $joinType,
2105 'entity' => $dst_entity,
2106 'definition' => $definition_join,
2107 'table' => $dst_entity->getDBTableName(),
2108 'alias' => $table_alias.$this->table_alias_postfix,
2109 'reference' => $csw_reference,
2110 'map_key' => $map_key
2111 );
2112
2113 $this->join_map[] = $join;
2114 $this->join_registry[$map_key] = $table_alias;
2115 }
2116 }
2117
2118 // set alias for each element
2119 $element->setParameter('talias', $table_alias.$this->table_alias_postfix);
2120
2121 $prev_alias = $table_alias;
2122 }
2123 }
2124 }
2125
2126 protected function buildSelect()
2127 {
2128 $sql = [];
2129
2130 $helper = $this->entity->getConnection()->getSqlHelper();
2131 $aliasLength = (int) $helper->getAliasLength();
2132
2133 foreach ($this->select_chains as $chain)
2134 {
2135 $definition = $chain->getSqlDefinition();
2136 $alias = $chain->getAlias();
2137
2138 if (strlen($alias) > $aliasLength)
2139 {
2140 // replace long aliases
2141 $newAlias = 'FALIAS_'.count($this->replaced_aliases);
2142 $this->replaced_aliases[$newAlias] = $alias;
2143
2144 $alias = $newAlias;
2145 }
2146
2147 $sql[] = $definition . ' AS ' . $helper->quote($alias);
2148 }
2149
2150 // empty select (or select forced primary only)
2151 if (empty($sql) ||
2152 (!empty($this->forcedObjectPrimaryFields) && count($sql) == count($this->forcedObjectPrimaryFields))
2153 )
2154 {
2155 $sql[] = 1;
2156 }
2157
2158 $strSql = join(",\n\t", $sql);
2159
2160 if ($this->hasDistinct() && $this->is_distinct)
2161 {
2162 // distinct by query settings, not by field
2163 $strSql = 'DISTINCT '.$strSql;
2164 }
2165
2166 return "\n\t".$strSql;
2167 }
2168
2173 protected function buildJoin()
2174 {
2175 $sql = array();
2176 $csw = new \CSQLWhere;
2177
2178 $connection = $this->entity->getConnection();
2179 $helper = $connection->getSqlHelper();
2180
2181 foreach ($this->join_map as $join)
2182 {
2183 // prepare csw fields
2184 $csw_fields = $this->getJoinCswFields($join['reference']);
2185 $csw->setFields($csw_fields);
2186
2187 if ($join['reference'] instanceof Filter)
2188 {
2189 $joinConditionSql = $join['reference']->getSql($this->global_chains);
2190 }
2191 else
2192 {
2193 $joinConditionSql = trim($csw->getQuery($join['reference']));
2194 }
2195
2196 // final sql
2197 $sql[] = sprintf('%s JOIN %s %s ON %s',
2198 $join['type'],
2199 $this->quoteTableSource($join['table']),
2200 $helper->quote($join['alias']),
2201 $joinConditionSql
2202 );
2203 }
2204
2205 return "\n".join("\n", $sql);
2206 }
2207
2213 protected function buildWhere()
2214 {
2215 $sql = array();
2216
2217 // old array filter
2218 if (!empty($this->where))
2219 {
2220 $csw = new \CSQLWhere;
2221
2222 $csw_fields = $this->getFilterCswFields($this->where);
2223 $csw->setFields($csw_fields);
2224
2225 $sql[] = trim($csw->getQuery($this->where));
2226 }
2227
2228 // new QueryFilter
2229 if ($this->whereHandler && $this->whereHandler->hasConditions())
2230 {
2231 // rewrite data doubling
2232 $this->rewriteDataDoubling($this->whereHandler, 'where');
2233
2234 $sql[] = $this->whereHandler->getSql($this->where_chains);
2235 }
2236
2237 return join(' AND ', array_filter($sql));
2238 }
2239
2244 protected function buildGroup()
2245 {
2246 $sql = array();
2247
2248 if ($this->hasAggregation())
2249 {
2250 // add non-aggr fields to group
2251 foreach ($this->global_chains as $chain)
2252 {
2253 $alias = $chain->getAlias();
2254
2255 // skip constants
2256 if ($chain->isConstant())
2257 {
2258 continue;
2259 }
2260
2261 if (isset($this->select_chains[$alias]) || isset($this->order_chains[$alias]) || isset($this->having_chains[$alias]))
2262 {
2263 if (isset($this->group_chains[$alias]))
2264 {
2265 // skip already grouped
2266 continue;
2267 }
2268 elseif (!$chain->hasAggregation() && !$chain->hasSubquery())
2269 {
2270 // skip subqueries and already aggregated
2271 $this->registerChain('group', $chain);
2272 }
2273 elseif (!$chain->hasAggregation() && $chain->hasSubquery() && $chain->getLastElement()->getValue() instanceof ExpressionField)
2274 {
2275 // but include build_from of subqueries
2276 $sub_chains = $chain->getLastElement()->getValue()->getBuildFromChains();
2277
2278 foreach ($sub_chains as $sub_chain)
2279 {
2280 // build real subchain starting from init entity
2281 $real_sub_chain = clone $chain;
2282
2283 foreach (array_slice($sub_chain->getAllElements(), 1) as $sub_chain_elem)
2284 {
2285 $real_sub_chain->addElement($sub_chain_elem);
2286 }
2287
2288 // add to query
2289 $this->registerChain('group', $this->global_chains[$real_sub_chain->getAlias()]);
2290 }
2291 }
2292 }
2293 elseif (isset($this->having_expr_chains[$alias]))
2294 {
2295 if (!$chain->hasAggregation() && $chain->hasSubquery())
2296 {
2297 $this->registerChain('group', $chain);
2298 }
2299 }
2300 }
2301 }
2302
2303 foreach ($this->group_chains as $chain)
2304 {
2305 $connection = $this->entity->getConnection();
2306 $sqlDefinition = $chain->getSqlDefinition();
2307 $valueField = $chain->getLastElement()->getValue();
2308
2309 if ($valueField instanceof ExpressionField)
2310 {
2311 $valueField = $valueField->getValueField();
2312 }
2313
2314 if (($connection instanceof Main\DB\OracleConnection || $connection instanceof Main\DB\MssqlConnection)
2315 && $valueField instanceof TextField)
2316 {
2317 // softTextCast
2318 $sqlDefinition = $connection->getSqlHelper()->softCastTextToChar($sqlDefinition);
2319 }
2320
2321 $sql[] = $sqlDefinition;
2322 }
2323
2324 return join(', ', $sql);
2325 }
2326
2332 protected function buildHaving()
2333 {
2334 $sql = array();
2335
2336 // old array filter
2337 if (!empty($this->having))
2338 {
2339 $csw = new \CSQLWhere;
2340
2341 $csw_fields = $this->getFilterCswFields($this->having);
2342 $csw->setFields($csw_fields);
2343
2344 $sql[] = trim($csw->getQuery($this->having));
2345 }
2346
2347 // new QueryFilter
2348 if ($this->havingHandler && $this->havingHandler->hasConditions())
2349 {
2350 // rewrite data doubling
2351 $this->rewriteDataDoubling($this->havingHandler, 'having');
2352
2353 $sql[] = $this->havingHandler->getSql($this->having_chains);
2354 }
2355
2356 return join(' AND ', array_filter($sql));
2357 }
2358
2363 protected function buildOrder()
2364 {
2365 $sql = array();
2366
2367 foreach ($this->order_chains as $chain)
2368 {
2369 $sort = isset($this->order[$chain->getDefinition()])
2370 ? $this->order[$chain->getDefinition()]
2371 : ($this->order[$chain->getAlias()] ?? '');
2372
2373 $connection = $this->entity->getConnection();
2374
2375 // define value field
2376 $valueField = $chain->getLastElement()->getValue();
2377 if ($valueField instanceof ExpressionField)
2378 {
2379 $valueField = $valueField->getValueField();
2380 }
2381
2382 // get final sql definition
2383 if (isset($this->select_chains[$chain->getAlias()]))
2384 {
2385 // optimization for fields that are in select already
2386 $alias = $chain->getAlias();
2387
2388 if ($key = array_search($alias, $this->replaced_aliases))
2389 {
2390 // alias was replaced
2391 $alias = $key;
2392 }
2393
2394 $sqlDefinition = $connection->getSqlHelper()->quote($alias);
2395 }
2396 else
2397 {
2398 $sqlDefinition = $chain->getSqlDefinition();
2399 }
2400
2401 if (($connection instanceof Main\DB\OracleConnection || $connection instanceof Main\DB\MssqlConnection)
2402 && $valueField instanceof TextField)
2403 {
2404 // softTextCast
2405 $sqlDefinition = $connection->getSqlHelper()->softCastTextToChar($sqlDefinition);
2406 }
2407
2408 $sql[] = $sqlDefinition. ' ' . $sort;
2409 }
2410
2411 return join(', ', $sql);
2412 }
2413
2421 protected function buildQuery($forceObjectPrimary = true)
2422 {
2423 $connection = $this->entity->getConnection();
2424 $helper = $connection->getSqlHelper();
2425
2426 if ($this->query_build_parts === null)
2427 {
2428
2429 foreach ($this->select as $key => $value)
2430 {
2431 $this->addToSelectChain($value, is_numeric($key) ? null : $key);
2432 }
2433
2434 $this->setFilterChains($this->filter);
2435 $this->divideFilter();
2436
2437 // unconditional entity scope
2438 $this->entity->setDefaultScope($this);
2439
2440 $this->setFilterHandlerChains($this->filterHandler);
2441 $this->divideFilterHandler();
2442
2443 foreach ($this->group as $value)
2444 {
2445 $this->addToGroupChain($value);
2446 }
2447
2448 foreach ($this->order as $key => $value)
2449 {
2450 $this->addToOrderChain($key);
2451 }
2452
2453 $this->buildJoinMap();
2454
2455 if ($forceObjectPrimary && empty($this->unionHandler))
2456 {
2457 $this->ensurePrimarySelect();
2458 }
2459
2460 $sqlJoin = $this->buildJoin();
2461
2462 $sqlSelect = $this->buildSelect();
2463 $sqlWhere = $this->buildWhere();
2464 $sqlGroup = $this->buildGroup();
2465 $sqlHaving = $this->buildHaving();
2466 $sqlOrder = $this->buildOrder();
2467
2468 $sqlFrom = $this->quoteTableSource($this->entity->getDBTableName());
2469
2470 $sqlFrom .= ' '.$helper->quote($this->getInitAlias());
2471 $sqlFrom .= ' '.$sqlJoin;
2472
2473 $this->query_build_parts = array_filter(array(
2474 'SELECT' => $sqlSelect,
2475 'FROM' => $sqlFrom,
2476 'WHERE' => $sqlWhere,
2477 'GROUP BY' => $sqlGroup,
2478 'HAVING' => $sqlHaving,
2479 'ORDER BY' => $sqlOrder
2480 ));
2481
2482 // ensure there are no private fields in query
2483 $this->checkForPrivateFields();
2484 }
2485
2486 $build_parts = $this->query_build_parts;
2487
2488 foreach ($build_parts as $k => &$v)
2489 {
2490 $v = $k . ' ' . $v;
2491 }
2492
2493 $query = join("\n", $build_parts);
2494
2495 if ($this->limit > 0)
2496 {
2497 $query = $helper->getTopSql($query, $this->limit, $this->offset);
2498 }
2499
2500 // union
2501 if (!empty($this->unionHandler))
2502 {
2503 if ($this->order || $this->limit)
2504 {
2505 $query = "({$query})";
2506 }
2507
2508 foreach ($this->unionHandler->getQueries() as $union)
2509 {
2510 $query .= " ".$union->getSql();
2511 }
2512
2513 // union sort
2514 if ($this->unionHandler->getOrder())
2515 {
2516 $sqlUnionOrder = array();
2517 foreach ($this->unionHandler->getOrder() as $definition => $sort)
2518 {
2519 $sqlDefinition = $connection->getSqlHelper()->quote(
2520 $this->global_chains[$definition]->getAlias()
2521 );
2522
2523 $sqlUnionOrder[] = $sqlDefinition . ' ' . $sort;
2524 }
2525
2526 $query .= ' ORDER BY ' . join(', ', $sqlUnionOrder);
2527 }
2528
2529 // union limit
2530 if ($this->unionHandler->getLimit())
2531 {
2532 $query = $helper->getTopSql($query, $this->unionHandler->getLimit(), $this->unionHandler->getOffset());
2533 }
2534 }
2535
2536 return $query;
2537 }
2538
2546 protected function getFilterCswFields(&$filter)
2547 {
2548 $fields = array();
2549
2550 foreach ($filter as $filter_def => &$filter_match)
2551 {
2552 if ($filter_def === 'LOGIC')
2553 {
2554 continue;
2555 }
2556
2557 if (!is_numeric($filter_def))
2558 {
2559 $sqlWhere = new \CSQLWhere();
2560 $csw_result = $sqlWhere->makeOperation($filter_def);
2561 list($definition, $operation) = array_values($csw_result);
2562
2563 $chain = $this->filter_chains[$definition];
2564 $last = $chain->getLastElement();
2565
2566 // need to create an alternative of CSQLWhere in D7.Entity
2567 $field_type = $last->getValue()->getDataType();
2568 $callback = null;
2569
2570 // rewrite type & value for CSQLWhere
2571 if (in_array($operation, array('SE', 'SN'), true)
2572 && in_array($filter_match, array(null, true, false), true)
2573 )
2574 {
2575 $field_type = 'callback';
2576
2577 if ($filter_match === null)
2578 {
2579 $callback = array($this, 'nullEqualityCallback');
2580 }
2581 else
2582 {
2583 // just boolean expression, without operator
2584 // e.g. WHERE EXISTS(...)
2585 $callback = array($this, 'booleanStrongEqualityCallback');
2586 }
2587 }
2588 elseif ($field_type == 'integer')
2589 {
2590 $field_type = 'int';
2591 }
2592 elseif ($field_type == 'boolean')
2593 {
2594 $field_type = 'string';
2595
2597 $field = $last->getValue();
2598 $values = $field->getValues();
2599
2600 if (is_numeric($values[0]) && is_numeric($values[1]))
2601 {
2602 $field_type = 'int';
2603 }
2604
2605 if (is_scalar($filter_match))
2606 {
2607 $filter_match = $field->normalizeValue($filter_match);
2608 }
2609 }
2610 elseif ($field_type == 'float')
2611 {
2612 $field_type = 'double';
2613 }
2614 elseif ($field_type == 'enum' || $field_type == 'text')
2615 {
2616 $field_type = 'string';
2617 }
2618
2619 $sqlDefinition = $chain->getSqlDefinition();
2620
2621 // data-doubling-off mode
2623 if ($chain->forcesDataDoublingOff() || ($this->data_doubling_off && $chain->hasBackReference()))
2624 {
2625 $primaryName = $this->entity->getPrimary();
2626 $uniquePostfix = '_TMP'.rand();
2627
2628 // build subquery
2629 $subQuery = new Query($this->entity);
2630 $subQuery->addSelect($primaryName);
2631 $subQuery->addFilter($filter_def, $filter_match);
2632 $subQuery->setTableAliasPostfix(strtolower($uniquePostfix));
2633 $subQuerySql = $subQuery->getQuery();
2634
2635 // proxying subquery as value to callback
2636 $filter_match = $subQuerySql;
2637 $callback = array($this, 'dataDoublingCallback');
2638
2639 $field_type = 'callback';
2640
2641 // change sql definition
2642 $idChain = $this->getRegisteredChain($primaryName);
2643 $sqlDefinition = $idChain->getSqlDefinition();
2644 }
2645
2646 // set entity connection to the sql expressions
2647 if ($filter_match instanceof Main\DB\SqlExpression)
2648 {
2649 $filter_match->setConnection($this->entity->getConnection());
2650 }
2651
2652 //$is_having = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
2653
2654 // if back-reference found (Entity:REF)
2655 // if NO_DOUBLING mode enabled, then change getSQLDefinition to subquery exists(...)
2656 // and those chains should not be in joins if it is possible
2657
2658 /*if (!$this->data_doubling && $chain->hasBackReference())
2659 {
2660 $field_type = 'callback';
2661 $init_query = $this;
2662
2663 $callback = function ($field, $operation, $value) use ($init_query, $chain)
2664 {
2665 $init_entity = $init_query->getEntity();
2666 $init_table_alias = CBaseEntity::camel2snake($init_entity->getName()).$init_query->getTableAliasPostfix();
2667
2668 $filter = array();
2669
2670 // add primary linking with main query
2671 foreach ($init_entity->getPrimaryArray() as $primary)
2672 {
2673 $filter['='.$primary] = new CSQLWhereExpression('?#', $init_table_alias.'.'.$primary);
2674 }
2675
2676 // add value filter
2677 $filter[CSQLWhere::getOperationByCode($operation).$chain->getDefinition()] = $value;
2678
2679 // build subquery
2680 $query_class = __CLASS__;
2681 $sub_query = new $query_class($init_entity);
2682 $sub_query->setFilter($filter);
2683 $sub_query->setTableAliasPostfix('_sub');
2684
2685 return 'EXISTS(' . $sub_query->getQuery() . ')';
2686 };
2687 }*/
2688
2689 $fields[$definition] = array(
2690 'TABLE_ALIAS' => 'table',
2691 'FIELD_NAME' => $sqlDefinition,
2692 'FIELD_TYPE' => $field_type,
2693 'MULTIPLE' => '',
2694 'JOIN' => '',
2695 'CALLBACK' => $callback
2696 );
2697 }
2698 elseif (is_array($filter_match))
2699 {
2700 $fields = array_merge($fields, $this->getFilterCswFields($filter_match));
2701 }
2702 }
2703
2704 return $fields;
2705 }
2706
2719 protected function prepareJoinReference($reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference)
2720 {
2721 $new = array();
2722
2723 foreach ($reference as $k => $v)
2724 {
2725 if ($k === 'LOGIC')
2726 {
2727 $new[$k] = $v;
2728 continue;
2729 }
2730
2731 if (is_numeric($k))
2732 {
2733 // subfilter, recursive call
2734 $new[$k] = $this->prepareJoinReference($v, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference);
2735 }
2736 else
2737 {
2738 // key
2739 $sqlWhere = new \CSQLWhere();
2740 $csw_result = $sqlWhere->makeOperation($k);
2741 list($field, $operation) = array_values($csw_result);
2742
2743 if (strpos($field, 'this.') === 0)
2744 {
2745 // parse the chain
2746 $definition = str_replace(\CSQLWhere::getOperationByCode($operation).'this.', '', $k);
2747 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
2748
2749 $chain = $this->getRegisteredChain($absDefinition, true);
2750
2751 if (!$isBackReference)
2752 {
2753 // make sure these fields will be joined before the main join
2754 $this->buildJoinMap(array($chain));
2755 }
2756 else
2757 {
2758 $chain->getLastElement()->setParameter('talias', $alias_this);
2759 }
2760
2761 // recursively collect all "build_from" fields
2762 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2763 {
2764 $this->collectExprChains($chain);
2765 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
2766
2767 foreach ($buildFrom as $bf)
2768 {
2769 // set base chain
2770 $baseChain = clone $chain;
2771
2772 // remove the last one - expression itself
2773 $baseChain->removeLastElement();
2774
2775 // remove parent entity for this child
2776 $bf->removeFirstElement();
2777
2778 // set new parents
2779 $bf->prepend($baseChain);
2780 }
2781
2782 $this->buildJoinMap($buildFrom);
2783 }
2784
2785 $k = \CSQLWhere::getOperationByCode($operation).$chain->getSqlDefinition();
2786 }
2787 elseif (strpos($field, 'ref.') === 0)
2788 {
2789 $definition = str_replace(\CSQLWhere::getOperationByCode($operation).'ref.', '', $k);
2790
2791 if (strpos($definition, '.') !== false)
2792 {
2793 throw new Main\ArgumentException(sprintf(
2794 'Reference chain `%s` is not allowed here. First-level definitions only.', $field
2795 ));
2796 }
2797
2798 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
2799 $chain = $this->getRegisteredChain($absDefinition, true);
2800
2801 if ($isBackReference)
2802 {
2803 // make sure these fields will be joined before the main join
2804 $this->buildJoinMap(array($chain));
2805 }
2806 else
2807 {
2808 $chain->getLastElement()->setParameter('talias', $alias_ref);
2809 }
2810
2811 // recursively collect all "build_from" fields
2812 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2813 {
2814 $this->collectExprChains($chain);
2815 $this->buildJoinMap($chain->getLastElement()->getValue()->getBuildFromChains());
2816 }
2817
2818 $k = \CSQLWhere::getOperationByCode($operation).$chain->getSqlDefinition();
2819 }
2820 else
2821 {
2822 throw new Main\SystemException(sprintf('Unknown reference key `%s`, it should start with "this." or "ref."', $k));
2823 }
2824
2825 // value
2826 if (is_array($v))
2827 {
2828 // field = expression
2829 $v = new \CSQLWhereExpression($v[0], array_slice($v, 1));
2830 }
2831 elseif ($v instanceof Main\DB\SqlExpression)
2832 {
2833 // set entity connection
2834 $v->setConnection($this->entity->getConnection());
2835 }
2836 elseif (!is_object($v))
2837 {
2838 if (strpos($v, 'this.') === 0)
2839 {
2840 $definition = str_replace('this.', '', $v);
2841 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
2842
2843 $chain = $this->getRegisteredChain($absDefinition, true);
2844
2845 if (!$isBackReference)
2846 {
2847 // make sure these fields will be joined before the main join
2848 $this->buildJoinMap(array($chain));
2849 }
2850 else
2851 {
2852 $chain->getLastElement()->setParameter('talias', $alias_this);
2853 }
2854
2855 // recursively collect all "build_from" fields
2856 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2857 {
2858 $this->collectExprChains($chain);
2859 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
2860
2861 foreach ($buildFrom as $bf)
2862 {
2863 // set base chain
2864 $baseChain = clone $chain;
2865
2866 // remove the last one - expression itself
2867 $baseChain->removeLastElement();
2868
2869 // remove parent entity for this child
2870 $bf->removeFirstElement();
2871
2872 // set new parents
2873 $bf->prepend($baseChain);
2874 }
2875
2876 $this->buildJoinMap($buildFrom);
2877 }
2878
2879 $field_def = $chain->getSqlDefinition();
2880 }
2881 elseif (strpos($v, 'ref.') === 0)
2882 {
2883 $definition = str_replace('ref.', '', $v);
2884
2885 if (strpos($definition, '.') !== false)
2886 {
2887 throw new Main\ArgumentException(sprintf(
2888 'Reference chain `%s` is not allowed here. First-level definitions only.', $v
2889 ));
2890 }
2891
2892 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
2893 $chain = $this->getRegisteredChain($absDefinition, true);
2894
2895 if ($isBackReference)
2896 {
2897 // make sure these fields will be joined before the main join
2898 $this->buildJoinMap(array($chain));
2899 }
2900 else
2901 {
2902 $chain->getLastElement()->setParameter('talias', $alias_ref);
2903 }
2904
2905 $this->buildJoinMap(array($chain));
2906
2907 // recursively collect all "build_from" fields
2908 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
2909 {
2910 // here could be one more check "First-level definitions only" for buildFrom elements
2911 $buildFromChains = $this->collectExprChains($chain);
2912
2913 // set same talias to buildFrom elements
2914 foreach ($buildFromChains as $buildFromChain)
2915 {
2916 if (!$isBackReference && $buildFromChain->getSize() > $chain->getSize())
2917 {
2918 throw new Main\ArgumentException(sprintf(
2919 'Reference chain `%s` is not allowed here. First-level definitions only.',
2920 $buildFromChain->getDefinition()
2921 ));
2922 }
2923
2924 if ($buildFromChain->getSize() === $chain->getSize())
2925 {
2926 // same entity, same table
2927 $buildFromChain->getLastElement()->setParameter('talias', $alias_ref);
2928 }
2929 }
2930
2931 $this->buildJoinMap($buildFromChains);
2932 }
2933
2934 $field_def = $chain->getSqlDefinition();
2935 }
2936 else
2937 {
2938 throw new Main\SystemException(sprintf('Unknown reference value `%s`', $v));
2939 }
2940
2941 $v = new \CSQLWhereExpression($field_def);
2942 }
2943 else
2944 {
2945 throw new Main\SystemException(sprintf('Unknown reference value `%s`, it should start with "this." or "ref."', $v));
2946 }
2947
2948 $new[$k] = $v;
2949 }
2950 }
2951
2952 return $new;
2953 }
2954
2968 protected function prepareJoinFilterReference(Filter $reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference, $firstCall = true)
2969 {
2970 // do not make an impact on original reference object
2971 if ($firstCall)
2972 {
2973 $reference = clone $reference;
2974 }
2975
2976 foreach ($reference->getConditions() as $condition)
2977 {
2978 if ($condition instanceof Filter)
2979 {
2980 // subfilter, recursive call
2982 $condition,
2983 $alias_this,
2984 $alias_ref,
2985 $baseDefinition,
2986 $refDefinition,
2987 $isBackReference,
2988 false
2989 );
2990 }
2991 else
2992 {
2993 // regular condition
2994 $field = $condition->getDefinition();
2995
2996 if (strpos($field, 'this.') === 0)
2997 {
2998 // parse the chain
2999 $definition = str_replace('this.', '', $field);
3000 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
3001
3002 $chain = $this->getRegisteredChain($absDefinition, true);
3003
3004 if (!$isBackReference)
3005 {
3006 // make sure these fields will be joined before the main join
3007 $this->buildJoinMap(array($chain));
3008 }
3009 else
3010 {
3011 $chain->getLastElement()->setParameter('talias', $alias_this);
3012 }
3013
3014 // recursively collect all "build_from" fields
3015 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3016 {
3017 $this->collectExprChains($chain);
3018 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
3019
3020 foreach ($buildFrom as $bf)
3021 {
3022 // set base chain
3023 $baseChain = clone $chain;
3024
3025 // remove the last one - expression itself
3026 $baseChain->removeLastElement();
3027
3028 // remove parent entity for this child
3029 $bf->removeFirstElement();
3030
3031 // set new parents
3032 $bf->prepend($baseChain);
3033 }
3034
3035 $this->buildJoinMap($buildFrom);
3036 }
3037
3038 $condition->setColumn($absDefinition);
3039 }
3040 elseif (strpos($field, 'ref.') === 0)
3041 {
3042 $definition = str_replace('ref.', '', $field);
3043
3044 if (strpos($definition, '.') !== false)
3045 {
3046 throw new Main\ArgumentException(sprintf(
3047 'Reference chain `%s` is not allowed here. First-level definitions only.', $field
3048 ));
3049 }
3050
3051 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
3052 $chain = $this->getRegisteredChain($absDefinition, true);
3053
3054 if ($isBackReference)
3055 {
3056 // make sure these fields will be joined before the main join
3057 $this->buildJoinMap(array($chain));
3058 }
3059 else
3060 {
3061 $chain->getLastElement()->setParameter('talias', $alias_ref);
3062 }
3063
3064 // recursively collect all "build_from" fields
3065 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3066 {
3067 $this->collectExprChains($chain);
3068 $this->buildJoinMap($chain->getLastElement()->getValue()->getBuildFromChains());
3069 }
3070
3071 $condition->setColumn($absDefinition);
3072 }
3073 else
3074 {
3075 throw new Main\SystemException(sprintf('Unknown reference key `%s`, it should start with "this." or "ref."', $field));
3076 }
3077
3078 // value
3079 $v = $condition->getValue();
3080
3081 if ($v instanceof Main\DB\SqlExpression)
3082 {
3083 // set entity connection
3084 $v->setConnection($this->entity->getConnection());
3085 }
3086 elseif ($v instanceof ColumnExpression)
3087 {
3088 if (strpos($v->getDefinition(), 'this.') === 0)
3089 {
3090 $definition = str_replace('this.', '', $v->getDefinition());
3091 $absDefinition = $baseDefinition <> ''? $baseDefinition.'.'.$definition : $definition;
3092
3093 $chain = $this->getRegisteredChain($absDefinition, true);
3094
3095 if (!$isBackReference)
3096 {
3097 // make sure these fields will be joined before the main join
3098 $this->buildJoinMap(array($chain));
3099 }
3100 else
3101 {
3102 $chain->getLastElement()->setParameter('talias', $alias_this);
3103 }
3104
3105 // recursively collect all "build_from" fields
3106 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3107 {
3108 $this->collectExprChains($chain);
3109 $buildFrom = $chain->getLastElement()->getValue()->getBuildFromChains();
3110
3111 foreach ($buildFrom as $bf)
3112 {
3113 // set base chain
3114 $baseChain = clone $chain;
3115
3116 // remove the last one - expression itself
3117 $baseChain->removeLastElement();
3118
3119 // remove parent entity for this child
3120 $bf->removeFirstElement();
3121
3122 // set new parents
3123 $bf->prepend($baseChain);
3124 }
3125
3126 $this->buildJoinMap($buildFrom);
3127 }
3128
3129 $v->setDefinition($absDefinition);
3130 }
3131 elseif (strpos($v->getDefinition(), 'ref.') === 0)
3132 {
3133 $definition = str_replace('ref.', '', $v->getDefinition());
3134
3135 if (strpos($definition, '.') !== false)
3136 {
3137 throw new Main\ArgumentException(sprintf(
3138 'Reference chain `%s` is not allowed here. First-level definitions only.', $v->getDefinition()
3139 ));
3140 }
3141
3142 $absDefinition = $refDefinition <> ''? $refDefinition.'.'.$definition : $definition;
3143 $chain = $this->getRegisteredChain($absDefinition, true);
3144
3145 if ($isBackReference)
3146 {
3147 // make sure these fields will be joined before the main join
3148 $this->buildJoinMap(array($chain));
3149 }
3150 else
3151 {
3152 $chain->getLastElement()->setParameter('talias', $alias_ref);
3153 }
3154
3155 $this->buildJoinMap(array($chain));
3156
3157 // recursively collect all "build_from" fields
3158 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
3159 {
3160 // here could be one more check "First-level definitions only" for buildFrom elements
3161 $buildFromChains = $this->collectExprChains($chain);
3162
3163 // set same talias to buildFrom elements
3164 foreach ($buildFromChains as $buildFromChain)
3165 {
3166 if (!$isBackReference && $buildFromChain->getSize() > $chain->getSize())
3167 {
3168 throw new Main\ArgumentException(sprintf(
3169 'Reference chain `%s` is not allowed here. First-level definitions only.',
3170 $buildFromChain->getDefinition()
3171 ));
3172 }
3173
3174 if ($buildFromChain->getSize() === $chain->getSize())
3175 {
3176 // same entity, same table
3177 $buildFromChain->getLastElement()->setParameter('talias', $alias_ref);
3178 }
3179 }
3180
3181 $this->buildJoinMap($buildFromChains);
3182 }
3183
3184 $v->setDefinition($absDefinition);
3185 }
3186 }
3187 }
3188 }
3189
3190 return $reference;
3191 }
3192
3193 protected function getJoinCswFields($reference)
3194 {
3195 $fields = array();
3196
3197 foreach ($reference as $k => $v)
3198 {
3199 if ($k === 'LOGIC')
3200 {
3201 continue;
3202 }
3203
3204 if (is_numeric($k))
3205 {
3206 $fields = array_merge($fields, $this->getJoinCswFields($v));
3207 }
3208 else
3209 {
3210 // key
3211 $sqlWhere = new \CSQLWhere();
3212 $csw_result = $sqlWhere->makeOperation($k);
3213 list($field, ) = array_values($csw_result);
3214
3215 $fields[$field] = array(
3216 'TABLE_ALIAS' => 'alias',
3217 'FIELD_NAME' => $field,
3218 'FIELD_TYPE' => 'string',
3219 'MULTIPLE' => '',
3220 'JOIN' => ''
3221 );
3222
3223 // no need to add values as csw fields
3224 }
3225 }
3226
3227 return $fields;
3228 }
3229
3236 protected function checkChainsAggregation($chain)
3237 {
3239 $chains = is_array($chain) ? $chain : array($chain);
3240
3241 foreach ($chains as $chain)
3242 {
3243 $last = $chain->getLastElement();
3244 $is_aggr = $last->getValue() instanceof ExpressionField && $last->getValue()->isAggregated();
3245
3246 if ($is_aggr)
3247 {
3248 return true;
3249 }
3250 }
3251
3252 return false;
3253 }
3254
3255 protected function checkChainsDistinct($chain)
3256 {
3258 $chains = is_array($chain) ? $chain : array($chain);
3259
3260 foreach ($chains as $chain)
3261 {
3262 $field = $chain->getLastElement()->getValue();
3263
3264 if ($field instanceof ExpressionField)
3265 {
3266 $expression = $field->getFullExpression();
3267 $expression = ExpressionField::removeSubqueries($expression);
3268
3269 preg_match_all('/(?:^|[^a-z0-9_])(DISTINCT)[\s\‍(]+/i', $expression, $matches);
3270
3271 if (!empty($matches[1]))
3272 {
3273 return true;
3274 }
3275 }
3276 }
3277
3278 return false;
3279 }
3280
3281 public function hasAggregation()
3282 {
3283 return !empty($this->group_chains) || !empty($this->having_chains)
3284 || $this->checkChainsAggregation($this->select_chains)
3285 || $this->checkChainsAggregation($this->order_chains);
3286 }
3287
3288 public function setDistinct($distinct = true)
3289 {
3290 $this->is_distinct = (bool) $distinct;
3291
3292 return $this;
3293 }
3294
3295 public function hasDistinct()
3296 {
3297 $distinctInSelect = $this->checkChainsDistinct($this->select_chains);
3298
3299 if ($distinctInSelect && $this->is_distinct)
3300 {
3301 // to avoid double distinct
3302 $this->is_distinct = false;
3303 }
3304
3305 return ($distinctInSelect || $this->is_distinct);
3306 }
3307
3317 protected function collectExprChains(Chain $chain, $storages = array('hidden'))
3318 {
3319 $last_elem = $chain->getLastElement();
3320 $bf_chains = $last_elem->getValue()->getBuildFromChains();
3321
3322 $pre_chain = clone $chain;
3323 //$pre_chain->removeLastElement();
3324 $scopedBuildFrom = [];
3325
3326 foreach ($bf_chains as $bf_chain)
3327 {
3328 // collect hidden chain
3329 $tmp_chain = clone $pre_chain;
3330
3331 // exclude init entity
3333 $bf_elements = array_slice($bf_chain->getAllElements(), 1);
3334
3335 // add elements
3336 foreach ($bf_elements as $bf_element)
3337 {
3338 $tmp_chain->addElement($bf_element);
3339 }
3340
3341 //if (!($bf_chain->getLastElement()->getValue() instanceof ExpressionField))
3342 {
3343 foreach ($storages as $storage)
3344 {
3345 $reg_chain = $this->registerChain($storage, $tmp_chain);
3346 }
3347
3348 // replace "build_from" chain end by registered chain end
3349 // actually it's better and more correctly to replace the whole chain
3350 $bf_chain->removeLastElement();
3352 $bf_chain->addElement($reg_chain->getLastElement());
3353
3354 // return buildFrom elements with original start of chain for this query
3355 $scoped_bf_chain = clone $pre_chain;
3356 $scoped_bf_chain->removeLastElement();
3357
3358 // copy tail from registered chain
3359 $tail = array_slice($reg_chain->getAllElements(), $pre_chain->getSize());
3360
3361 foreach ($tail as $tailElement)
3362 {
3363 $scoped_bf_chain->addElement($tailElement);
3364 }
3365
3366 $scopedBuildFrom[] = $scoped_bf_chain;
3367 }
3368
3369 // check elements to recursive collect hidden chains
3370 foreach ($bf_elements as $bf_element)
3371 {
3372 if ($bf_element->getValue() instanceof ExpressionField)
3373 {
3374 $this->collectExprChains($tmp_chain);
3375 }
3376 }
3377 }
3378
3379 return $scopedBuildFrom;
3380 }
3381
3386 protected function getUnionHandler()
3387 {
3388 if ($this->unionHandler === null)
3389 {
3390 $this->unionHandler = new Union($this->entity->getConnection());
3391 }
3392
3393 return $this->unionHandler;
3394 }
3395
3396 public function registerChain($section, Chain $chain, $opt_key = null)
3397 {
3398 $alias = $chain->getAlias();
3399
3400 if (isset($this->global_chains[$alias]))
3401 {
3402 if ($this->global_chains[$alias]->getDefinition() == $chain->getDefinition())
3403 {
3404 $reg_chain = $this->global_chains[$alias];
3405 }
3406 else
3407 {
3408 // we have a collision
3409 // like book.author_id and book.author.id have the same aliases, but different definitions
3410 // in most of the cases it's not a problem, there would be the same expected data
3411 // but we need register this chain separately to be available for internal usage
3412 $reg_chain = $chain;
3413
3414 $this->global_chains[$reg_chain->getDefinition()] = $chain;
3415
3416 // or should we make unique alias and register with it?
3417 $alias = $this->getUniqueAlias();
3418 $chain->setCustomAlias($alias);
3419 $this->global_chains[$alias] = $chain;
3420 }
3421 }
3422 else
3423 {
3424 $reg_chain = $chain;
3425 $def = $reg_chain->getDefinition();
3426
3427 $this->global_chains[$alias] = $chain;
3428 $this->global_chains[$def] = $chain;
3429 }
3430
3431 $storage_name = $section . '_chains';
3432
3433 // in case of collision do not rewrite by alias
3434 if (!isset($this->{$storage_name}[$alias]))
3435 {
3436 $this->{$storage_name}[$alias] = $reg_chain;
3437 // should we store by definition too?
3438 }
3439
3440 if (!is_null($opt_key))
3441 {
3442 $this->{$storage_name}[$opt_key] = $reg_chain;
3443 }
3444
3445 return $reg_chain;
3446 }
3447
3456 public function getRegisteredChain($key, $force_create = false)
3457 {
3458 if (isset($this->global_chains[$key]))
3459 {
3460 return $this->global_chains[$key];
3461 }
3462
3463 if ($force_create)
3464 {
3465 $chain = Chain::getChainByDefinition($this->entity, $key);
3466 $this->registerChain('global', $chain);
3467
3468 return $chain;
3469 }
3470
3471 return false;
3472 }
3473
3474 protected function getUniqueAlias()
3475 {
3476 return 'UALIAS_'.($this->uniqueAliasCounter++);
3477 }
3478
3479 public function booleanStrongEqualityCallback($field, $operation, $value)
3480 {
3481 $value = ($operation == 'SE') ? $value : !$value;
3482 return ($value ? '' : 'NOT ') . $field;
3483 }
3484
3485 public function nullEqualityCallback($field, $operation, $value)
3486 {
3487 return $field.' IS '.($operation == 'SE' ? '' : 'NOT ') . 'NULL';
3488 }
3489
3490 public function dataDoublingCallback($field, $operation, $value)
3491 {
3492 return $field.' IN ('.$value.')';
3493 }
3494
3503 protected function query($query)
3504 {
3505 // check nosql configuration
3506 $connection = $this->entity->getConnection();
3507 $configuration = $connection->getConfiguration();
3508
3510 $result = null;
3511
3512 if (isset($configuration['handlersocket']['read']))
3513 {
3514 // optimize through nosql
3515 $nosqlConnectionName = $configuration['handlersocket']['read'];
3516
3517 $nosqlConnection = Main\Application::getInstance()->getConnectionPool()->getConnection($nosqlConnectionName);
3518 $isNosqlCapable = NosqlPrimarySelector::checkQuery($nosqlConnection, $this);
3519
3520 if ($isNosqlCapable)
3521 {
3522 $nosqlResult = NosqlPrimarySelector::relayQuery($nosqlConnection, $this);
3523 $result = new Main\DB\ArrayResult($nosqlResult);
3524
3525 // add data converters
3526 if (!empty($nosqlResult))
3527 {
3529 $converters = [];
3530
3531 foreach ($this->getSelectChains() as $selectChain)
3532 {
3533 $field = $selectChain->getLastElement()->getValue();
3534
3535 if ($field instanceof ScalarField)
3536 {
3537 $converter = $connection->getSqlHelper()->getConverter($field);
3538
3539 if (is_callable($converter))
3540 {
3541 $converter[$selectChain->getAlias()] = $converter;
3542 }
3543 }
3544 }
3545
3546 if (!empty($converters))
3547 {
3548 $result->setConverters($converters);
3549 }
3550 }
3551 }
3552 }
3553
3554 if ($result === null)
3555 {
3556 // regular SQL query
3557 $result = $connection->query($query);
3558 $result->setReplacedAliases($this->replaced_aliases);
3559
3560 if($this->countTotal)
3561 {
3562 if ($this->limit && ($result->getSelectedRowsCount() < $this->limit))
3563 {
3564 // optimization for first and last pages
3565 $result->setCount((int) $this->offset + $result->getSelectedRowsCount());
3566 }
3567 elseif (empty($this->limit))
3568 {
3569 // optimization for queries without limit
3570 $result->setCount($result->getSelectedRowsCount());
3571 }
3572 else
3573 {
3574 // dedicated query
3575 $result->setCount($this->queryCountTotal());
3576 }
3577 }
3578
3579 static::$last_query = $query;
3580 }
3581
3582 if ($this->isFetchModificationRequired())
3583 {
3584 $result->addFetchDataModifier(array($this, 'fetchDataModificationCallback'));
3585 }
3586
3587 return $result;
3588 }
3589
3590 public function queryCountTotal()
3591 {
3592 if ($this->query_build_parts === null)
3593 {
3594 $this->buildQuery();
3595 }
3596
3597 $buildParts = $this->query_build_parts;
3598
3599 //remove order
3600 unset($buildParts['ORDER BY']);
3601
3602 //remove select
3603 $buildParts['SELECT'] = "1 cntholder";
3604
3605 foreach ($buildParts as $k => &$v)
3606 {
3607 $v = $k . ' ' . $v;
3608 }
3609
3610 $cntQuery = join("\n", $buildParts);
3611
3612 // select count
3613 $cntQuery =
3614 "SELECT COUNT(cntholder) AS TMP_ROWS_CNT FROM ({$cntQuery}) xxx";
3615
3616 return $this->entity->getConnection()->queryScalar($cntQuery);
3617 }
3618
3623 public function fetchDataModificationCallback(&$data)
3624 {
3625 // entity-defined callbacks
3626 foreach ($this->selectFetchModifiers as $alias => $modifiers)
3627 {
3628 foreach ($modifiers as $modifier)
3629 {
3630 $data[$alias] = call_user_func_array($modifier, array($data[$alias], $this, $data, $alias));
3631 }
3632 }
3633 }
3634
3641 {
3642 $this->selectFetchModifiers = array();
3643
3644 foreach ($this->select_chains as $chain)
3645 {
3646 if ($chain->getLastElement()->getValue()->getFetchDataModifiers())
3647 {
3648 $this->selectFetchModifiers[$chain->getAlias()] = $chain->getLastElement()->getValue()->getFetchDataModifiers();
3649 }
3650 }
3651
3652 return !empty($this->selectFetchModifiers) || !empty($this->files);
3653 }
3654
3662 protected function replaceSelectAliases($query)
3663 {
3664 $connection = $this->entity->getConnection();
3665 $helper = $connection->getSqlHelper();
3666
3667 $length = (int) $helper->getAliasLength();
3668 $leftQuote = $helper->getLeftQuote();
3669 $rightQuote = $helper->getRightQuote();
3670
3671 $replaced = array();
3672
3673 preg_match_all(
3674 '/ AS '.preg_quote($leftQuote).'([a-z0-9_]{'.($length+1).',})'.preg_quote($rightQuote).'/i',
3675 $query, $matches
3676 );
3677
3678 if (!empty($matches[1]))
3679 {
3680 foreach ($matches[1] as $alias)
3681 {
3682 $newAlias = 'FALIAS_'.count($replaced);
3683 $replaced[$newAlias] = $alias;
3684
3685 $query = str_replace(
3686 ' AS ' . $helper->quote($alias),
3687 ' AS ' . $helper->quote($newAlias) . '/* '.$alias.' */',
3688 $query
3689 );
3690 }
3691 }
3692
3693 return array($query, $replaced);
3694 }
3695
3702 public function quoteTableSource($source)
3703 {
3704 // don't quote subqueries
3705 if (!preg_match('/\s*\‍(\s*SELECT.*\‍)\s*/is', $source))
3706 {
3707 $source = $this->entity->getConnection()->getSqlHelper()->quote($source);
3708 }
3709
3710 return $source;
3711 }
3712
3713 public function __clone()
3714 {
3715 $this->entity = clone $this->entity;
3716
3717 $this->filterHandler = clone $this->filterHandler;
3718 $this->whereHandler = clone $this->whereHandler;
3719 $this->havingHandler = clone $this->havingHandler;
3720
3721 foreach ($this->select as $k => $v)
3722 {
3723 if ($v instanceof ExpressionField)
3724 {
3725 $this->select[$k] = clone $v;
3726 }
3727 }
3728 }
3729
3734 public function hasBackReference()
3735 {
3736 if (empty($this->global_chains))
3737 {
3738 throw new Main\SystemException('Query has not been executed or built');
3739 }
3740
3741 foreach ($this->global_chains as $chain)
3742 {
3743 if ($chain->hasBackReference())
3744 {
3745 return true;
3746 }
3747 }
3748
3749 return false;
3750 }
3751
3755 public function getChains()
3756 {
3757 return $this->global_chains;
3758 }
3759
3763 public function getGroupChains()
3764 {
3765 return $this->group_chains;
3766 }
3767
3771 public function getHiddenChains()
3772 {
3773 return $this->hidden_chains;
3774 }
3775
3779 public function getHavingChains()
3780 {
3781 return $this->having_chains;
3782 }
3783
3787 public function getFilterChains()
3788 {
3789 return $this->filter_chains;
3790 }
3791
3795 public function getOrderChains()
3796 {
3797 return $this->order_chains;
3798 }
3799
3803 public function getSelectChains()
3804 {
3805 return $this->select_chains;
3806 }
3807
3811 public function getWhereChains()
3812 {
3813 return $this->where_chains;
3814 }
3815
3819 public function getRuntimeChains()
3820 {
3821 return $this->runtime_chains;
3822 }
3823
3824 public function getJoinMap()
3825 {
3826 return $this->join_map;
3827 }
3828
3838 public function getQuery($forceObjectPrimary = false)
3839 {
3840 return $this->buildQuery($forceObjectPrimary);
3841 }
3842
3848 public static function getLastQuery()
3849 {
3850 return static::$last_query;
3851 }
3852
3853 public function getEntity()
3854 {
3855 return $this->entity;
3856 }
3857
3869 public static function buildFilterSql(Entity $entity, $filter)
3870 {
3871 $query = new static($entity);
3872
3873 if ($filter instanceof Filter)
3874 {
3875 // new object filter
3876 $query->where($filter);
3877 }
3878 else
3879 {
3880 // old array filter
3881 $query->setFilter($filter);
3882 }
3883
3884 $query->setCustomBaseTableAlias($entity->getDBTableName())->buildQuery();
3885
3886 return $query->query_build_parts['WHERE'];
3887 }
3888
3895 public function getInitAlias($withPostfix = true)
3896 {
3897 if ($this->custom_base_table_alias !== null)
3898 {
3899 return $this->custom_base_table_alias;
3900 }
3901
3902 $init_alias = strtolower($this->entity->getCode());
3903
3904 // add postfix
3905 if ($withPostfix)
3906 {
3907 $init_alias .= $this->table_alias_postfix;
3908 }
3909
3910 // check length
3911 $connection = $this->entity->getConnection();
3912 $aliasLength = $connection->getSqlHelper()->getAliasLength();
3913
3914 if (strlen($init_alias) > $aliasLength)
3915 {
3916 $init_alias = 'base';
3917
3918 // add postfix
3919 if ($withPostfix)
3920 {
3921 $init_alias .= $this->table_alias_postfix;
3922 }
3923 }
3924
3925 return $init_alias;
3926 }
3927
3928 public function getReplacedAliases()
3929 {
3930 return $this->replaced_aliases;
3931 }
3932
3933 /*
3934 * Sets cache TTL in seconds.
3935 * @param int $ttl
3936 * @return $this
3937 */
3938 public function setCacheTtl($ttl)
3939 {
3940 $this->cacheTtl = (int)$ttl;
3941 return $this;
3942 }
3943
3949 public function cacheJoins($mode)
3950 {
3951 $this->cacheJoins = (bool)$mode;
3952 return $this;
3953 }
3954
3955 public function dump()
3956 {
3957 echo '<pre>';
3958
3959 echo 'last query: ';
3960 var_dump(static::$last_query);
3961 echo PHP_EOL;
3962
3963 echo 'size of select_chains: '.count($this->select_chains);
3964 echo PHP_EOL;
3965 foreach ($this->select_chains as $num => $chain)
3966 {
3967 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
3968 $chain->dump();
3969 echo PHP_EOL;
3970 }
3971
3972 echo PHP_EOL.PHP_EOL;
3973
3974 echo 'size of where_chains: '.count($this->where_chains);
3975 echo PHP_EOL;
3976 foreach ($this->where_chains as $num => $chain)
3977 {
3978 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
3979 $chain->dump();
3980 echo PHP_EOL;
3981 }
3982
3983 echo PHP_EOL.PHP_EOL;
3984
3985 echo 'size of group_chains: '.count($this->group_chains);
3986 echo PHP_EOL;
3987 foreach ($this->group_chains as $num => $chain)
3988 {
3989 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
3990 $chain->dump();
3991 echo PHP_EOL;
3992 }
3993
3994 echo PHP_EOL.PHP_EOL;
3995
3996 echo 'size of having_chains: '.count($this->having_chains);
3997 echo PHP_EOL;
3998 foreach ($this->having_chains as $num => $chain)
3999 {
4000 echo ' chain ['.$num.'] has '.$chain->getSize().' elements: '.PHP_EOL;
4001 $chain->dump();
4002 echo PHP_EOL;
4003 }
4004
4005 echo PHP_EOL.PHP_EOL;
4006
4007 echo 'size of filter_chains: '.count($this->filter_chains);
4008 echo PHP_EOL;
4009 foreach ($this->filter_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 select_expr_chains: '.count($this->select_expr_chains);
4019 echo PHP_EOL;
4020 foreach ($this->select_expr_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 hidden_chains: '.count($this->hidden_chains);
4030 echo PHP_EOL;
4031 foreach ($this->hidden_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 global_chains: '.count($this->global_chains);
4041 echo PHP_EOL;
4042 foreach ($this->global_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 var_dump($this->join_map);
4052
4053 echo '</pre>';
4054 }
4055}
static getInstance($entityName)
Definition entity.php:103
getDefinition($elementsSlice=0)
Definition chain.php:153
static relayQuery(\Bitrix\Main\Data\Connection $connection, Query $query)
static checkQuery(\Bitrix\Main\Data\Connection $connection, Query $query)
static isFieldPrivate($field)
Definition query.php:801
setFilter(array $filter)
Definition query.php:381
setDistinct($distinct=true)
Definition query.php:3288
registerChain($section, Chain $chain, $opt_key=null)
Definition query.php:3396
buildQuery($forceObjectPrimary=true)
Definition query.php:2421
addToGroupChain($definition)
Definition query.php:1846
addToOrderChain($definition)
Definition query.php:1862
fetch(\Bitrix\Main\Text\Converter $converter=null)
Definition query.php:981
nullEqualityCallback($field, $operation, $value)
Definition query.php:3485
dataDoublingCallback($field, $operation, $value)
Definition query.php:3490
addOrder($definition, $order='ASC')
Definition query.php:508
registerRuntimeField($name, $fieldInfo=null)
Definition query.php:831
fetchDataModificationCallback(&$data)
Definition query.php:3623
addFilter($key, $value)
Definition query.php:394
setSelect(array $select)
Definition query.php:338
addSelect($definition, $alias='')
Definition query.php:351
booleanStrongEqualityCallback($field, $operation, $value)
Definition query.php:3479
prepareJoinFilterReference(Filter $reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference, $firstCall=true)
Definition query.php:2968
addUnionOrder($definition, $order='ASC')
Definition query.php:655
static buildFilterSql(Entity $entity, $filter)
Definition query.php:3869
__call($method, $arguments)
Definition query.php:280
setTableAliasPostfix($postfix)
Definition query.php:866
checkFilterHandlerAggregation(Filter $filter)
Definition query.php:1750
prepareJoinReference($reference, $alias_this, $alias_ref, $baseDefinition, $refDefinition, $isBackReference)
Definition query.php:2719
getQuery($forceObjectPrimary=false)
Definition query.php:3838
getInitAlias($withPostfix=true)
Definition query.php:3895
fetchAll(\Bitrix\Main\Text\Converter $converter=null)
Definition query.php:995
getRegisteredChain($key, $force_create=false)
Definition query.php:3456
rewriteDataDoubling(Filter $filter, $section)
Definition query.php:1801
getJoinCswFields($reference)
Definition query.php:3193
countTotal($count=null)
Definition query.php:578