Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
expressionfield.php
1<?php
10
17
24class ExpressionField extends Field implements IReadable
25{
29 protected $expression;
30
35 protected $fullExpression;
36
38 protected $valueType;
39
43 protected $valueField;
44
48 protected $buildFrom;
49
52
53 protected $isAggregated;
54
55 protected $hasSubquery;
56
57 protected static
58 $aggrFunctionsMYSQL = array('AVG', 'BIT_AND', 'BIT_OR', 'BIT_XOR', 'COUNT',
59 'GROUP_CONCAT', 'MAX', 'MIN', 'STD', 'STDDEV_POP', 'STDDEV_SAMP',
60 'STDDEV', 'SUM', 'VAR_POP', 'VAR_SAMP', 'VARIANCE'
61 ),
62 $aggrFunctionsMSSQL = array('AVG', 'MIN', 'CHECKSUM_AGG', 'OVER', 'COUNT',
63 'ROWCOUNT_BIG', 'COUNT_BIG', 'STDEV', 'GROUPING', 'STDEVP',
64 'GROUPING_ID', 'SUM', 'MAX', 'VAR', 'VARP'
65 ),
66 $aggrFunctionsORACLE = array('AVG', 'COLLECT', 'CORR', 'CORR_S', 'CORR_K',
67 'COUNT', 'COVAR_POP', 'COVAR_SAMP', 'CUME_DIST', 'DENSE_RANK', 'FIRST',
68 'GROUP_ID', 'GROUPING', 'GROUPING_ID', 'LAST', 'MAX', 'MEDIAN', 'MIN',
69 'PERCENTILE_CONT', 'PERCENTILE_DISC', 'PERCENT_RANK', 'RANK',
70 'REGR_SLOPE', 'REGR_INTERCEPT', 'REGR_COUNT', 'REGR_R2', 'REGR_AVGX',
71 'REGR_AVGY', 'REGR_SXX', 'REGR_SYY', 'REGR_SXY', 'STATS_BINOMIAL_TEST',
72 'STATS_CROSSTAB', 'STATS_F_TEST', 'STATS_KS_TEST', 'STATS_MODE',
73 'STATS_MW_TEST', 'STATS_ONE_WAY_ANOVA', 'STATS_T_TEST_ONE',
74 'STATS_T_TEST_PAIRED', 'STATS_T_TEST_INDEP', 'STATS_T_TEST_INDEPU',
75 'STATS_WSR_TEST', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 'SUM',
76 'VAR_POP', 'VAR_SAMP', 'VARIANCE'
77 ),
79
91 public function __construct($name, $expression, $buildFrom = null, $parameters = array())
92 {
93 if (!isset($parameters['data_type']))
94 {
95 $parameters['data_type'] = 'string'; // deprecated
96
97 $this->valueType = StringField::class;
98 }
99
100 parent::__construct($name, $parameters);
101
102 $this->expression = $expression;
103
104 if (!is_array($buildFrom) && $buildFrom !== null)
105 {
106 $buildFrom = array($buildFrom);
107 }
108 elseif ($buildFrom === null)
109 {
110 $buildFrom = array();
111 }
112
113 $this->buildFrom = $buildFrom;
114 }
115
116 public function __call($name, $arguments)
117 {
118 return call_user_func_array(array($this->valueField, $name), $arguments);
119 }
120
124 public function getTypeMask()
125 {
127 }
128
134 public function configureValueType($class)
135 {
136 $this->valueType = $class;
137 return $this;
138 }
139
144 public function configureValueField($field)
145 {
146 $this->valueField = $field;
147 $this->valueType = get_class($field);
148
149 return $this;
150 }
151
158 public function setEntity(Entity $entity)
159 {
160 parent::setEntity($entity);
161
162 $parameters = $this->initialParameters;
163 unset($parameters['expression']);
164
165 if ($this->valueType !== null)
166 {
167 if ($this->valueField === null)
168 {
170 $valueField = new $this->valueType($this->name, $parameters);
171 $this->valueField = $this->entity->initializeField($this->name, $valueField);
172 }
173 }
174 else
175 {
176 // deprecated - old format with parameters and data_type
177 $this->valueField = $this->entity->initializeField($this->name, $parameters);
178 $this->valueType = get_class($this->valueField);
179 }
180
181 if (!($this->valueField instanceof ScalarField))
182 {
183 throw new SystemException('expression field can only be a scalar type.');
184 }
185 }
186
187 public function getExpression()
188 {
189 return $this->expression;
190 }
191
195 public function getBuildFrom()
196 {
197 return $this->buildFrom;
198 }
199
204 public function getFullExpression()
205 {
206 if (!isset($this->fullExpression))
207 {
208 $SQLBuildFrom = array();
210
211 foreach ($this->buildFrom as $element)
212 {
213 if ($element instanceof \Closure)
214 {
216 // no need to get real value. also it may [] to false positive check in hasAggregation or hasSubquery
217 //$sqlExpression = $element();
218 //$SQLBuildFrom[] = $sqlExpression->compile();
219 $SQLBuildFrom[] = '';
220 }
221 else
222 {
223 $chain = array_shift($buildFromChains);
224
225 if ($chain->getLastElement()->getValue() instanceof ExpressionField)
226 {
227 $SQLBuildFrom[] = $chain->getLastElement()->getValue()->getFullExpression();
228 }
229 else
230 {
231 $SQLBuildFrom[] = '%s';
232 }
233 }
234 }
235
236 $this->fullExpression = call_user_func_array('sprintf', array_merge(array($this->expression), $SQLBuildFrom));
237 }
238
240 }
241
246 public function isAggregated()
247 {
248 if (!isset($this->isAggregated))
249 {
250 $this->isAggregated = (bool) self::checkAggregation($this->getFullExpression());
251 }
252
253 return $this->isAggregated;
254 }
255
260 public function hasSubquery()
261 {
262 if (!isset($this->hasSubquery))
263 {
264 $this->hasSubquery = (bool) self::checkSubquery($this->getFullExpression());
265 }
266
267 return $this->hasSubquery;
268 }
269
270 public function isConstant()
271 {
272 return empty($this->buildFrom);
273 }
274
280 public function getBuildFromChains()
281 {
282 if (is_null($this->buildFromChains))
283 {
284 $this->buildFromChains = array();
285
286 foreach ($this->buildFrom as $elem)
287 {
288 if (!($elem instanceof \Closure))
289 {
290 // validate if build from scalar or expression
291 $chain = Chain::getChainByDefinition($this->entity, $elem);
292 $field = $chain->getLastElement()->getValue();
293
294 if ($field instanceof ScalarField || $field instanceof ExpressionField)
295 {
296 $this->buildFromChains[] = $chain;
297 }
298 else
299 {
300 throw new SystemException(sprintf(
301 'Expected ScalarField or ExpressionField in `%s` build_from, but `%s` was given.',
302 $this->name, is_object($field) ? get_class($field).':'.$field->getName() : gettype($field)
303 ));
304 }
305 }
306 }
307 }
308
310 }
311
312 public static function checkAggregation($expression)
313 {
314 if (empty(self::$aggrFunctions))
315 {
316 self::$aggrFunctions = array_unique(array_merge(
317 self::$aggrFunctionsMYSQL, self::$aggrFunctionsMSSQL, self::$aggrFunctionsORACLE
318 ));
319 }
320
321 // should remove subqueries from expression here: EXISTS(..(..)..), (SELECT ..(..)..)
322 $expression = static::removeSubqueries($expression);
323
324 // then check for aggr functions
325 preg_match_all('/(?:^|[^a-z0-9_])('.join('|', self::$aggrFunctions).')[\s\‍(]+/i', $expression, $matches);
326
327 return $matches[1] ?? null;
328 }
329
330 public static function checkSubquery($expression)
331 {
332 return (preg_match('/(?:^|[^a-zA-Z0-9_])EXISTS\s*\‍(/i', $expression) || preg_match('/(?:^|[^a-zA_Z0-9_])\‍(\s*SELECT/i', $expression));
333 }
334
335 public static function removeSubqueries($expression)
336 {
337 // remove double slashes
338 $expression = str_replace('\\\\\\\\', '', $expression);
339
340 // remove strings
341 $expression = static::removeStrings('"', $expression);
342 $expression = static::removeStrings("'", $expression);
343
344 // remove subqueries' bodies
345 $clear = static::removeSubqueryBody($expression);
346
347 while ($clear !== $expression)
348 {
349 $expression = $clear;
350 $clear = static::removeSubqueryBody($expression);
351 }
352
353 return $clear;
354 }
355
356 protected static function removeStrings($quote, $expression)
357 {
358 // remove escaped quotes
359 $expression = str_replace('\\' . $quote, '', $expression);
360
361 // remove quoted strings
362 $expression = preg_replace('/' . $quote . '.*?' . $quote . '/', '', $expression);
363
364 return $expression;
365 }
366
367 protected static function removeSubqueryBody($query)
368 {
369 $subqPattern = '\‍(\s*SELECT\s+';
370
371 $matches = null;
372 preg_match('/' . $subqPattern . '/i', $query, $matches);
373
374 if (!empty($matches))
375 {
376 $substring = $matches[0];
377
378 $subqPosition = mb_strpos($query, $substring);
379 $subqStartPosition = $subqPosition + mb_strlen($substring);
380
381 $bracketsCount = 1;
382 $currentPosition = $subqStartPosition;
383
384 // until initial bracket is closed
385 while ($bracketsCount > 0)
386 {
387 $symbol = mb_substr($query, $currentPosition, 1);
388
389 if ($symbol == '')
390 {
391 // end of string
392 break;
393 }
394
395 if ($symbol == '(')
396 {
397 $bracketsCount++;
398 }
399 elseif ($symbol == ')')
400 {
401 $bracketsCount--;
402 }
403
404 $currentPosition++;
405 }
406
407 $query = mb_substr($query, 0, $subqPosition).mb_substr($query, $currentPosition);
408 }
409
410 return $query;
411 }
412
417 public function getDataType()
418 {
419 return $this->valueField->getDataType();
420 }
421
425 public function getValueType()
426 {
427 return $this->valueType;
428 }
429
433 public function getValueField()
434 {
435 return $this->valueField;
436 }
437
438 public function __clone()
439 {
440 $this->buildFromChains = null;
441 $this->fullExpression = null;
442 }
443
449 public function cast($value)
450 {
452 return $valueField->cast($value);
453 }
454
460 public function convertValueFromDb($value)
461 {
463 return $valueField->convertValueFromDb($value);
464 }
465
471 public function convertValueToDb($value)
472 {
475 return $valueField->convertValueToDb($value);
476 }
477
478 public function validateValue($value, $primary, $row, Result $result)
479 {
480 throw new NotImplementedException;
481 }
482}
483
484
__construct($name, $expression, $buildFrom=null, $parameters=array())
static removeStrings($quote, $expression)
validateValue($value, $primary, $row, Result $result)
setEntity(Entity $entity)
Definition field.php:144