Skip to content

Commit

Permalink
remove NULL from simple cases of SUM GROUP BY etc
Browse files Browse the repository at this point in the history
  • Loading branch information
schlndh committed Jun 22, 2024
1 parent ca4c06d commit 3080e3d
Show file tree
Hide file tree
Showing 23 changed files with 189 additions and 23 deletions.
70 changes: 51 additions & 19 deletions src/Analyser/AnalyserState.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ private function analyseSingleSelectQuery(SimpleSelectQuery $select): array
case SelectExprTypeEnum::REGULAR_EXPR:
assert($selectExpr instanceof RegularExpr);
$expr = $this->removeUnaryPlusPrefix($selectExpr->expr);
$resolvedExpr = $this->resolveExprType($expr);
$resolvedExpr = $this->resolveExprType($expr, null, $select->groupBy !== null);
$resolvedField = new QueryResultField(
$selectExpr->alias ?? $this->getDefaultFieldNameForExpr($expr),
$resolvedExpr,
Expand Down Expand Up @@ -781,8 +781,12 @@ private function analyseTruncateQuery(TruncateQuery $query): void
}

/** @throws AnalyserException */
private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $condition = null): ExprTypeResult
{
private function resolveExprType(
Expr\Expr $expr,
?AnalyserConditionTypeEnum $condition = null,
// false doesn't mean that the result set is empty, it may be empty.
bool $isNonEmptyAggResultSet = false,
): ExprTypeResult {
// TODO: handle all expression types
switch ($expr::getExprType()) {
case Expr\ExprTypeEnum::COLUMN:
Expand Down Expand Up @@ -851,7 +855,11 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
AnalyserConditionTypeEnum::NULL => AnalyserConditionTypeEnum::NULL,
default => AnalyserConditionTypeEnum::NOT_NULL,
};
$timeQuantityResult = $this->resolveExprType($expr->timeQuantity, $innerCondition);
$timeQuantityResult = $this->resolveExprType(
$expr->timeQuantity,
$innerCondition,
$isNonEmptyAggResultSet,
);

return new ExprTypeResult(
new Schema\DbType\DateTimeType(),
Expand Down Expand Up @@ -880,7 +888,11 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
$isTruthinessUncertain = true;
}

$resolvedInnerExpr = $this->resolveExprType($expr->expression, $innerCondition);
$resolvedInnerExpr = $this->resolveExprType(
$expr->expression,
$innerCondition,
$isNonEmptyAggResultSet,
);

$type = match ($expr->operation) {
Expr\UnaryOpTypeEnum::PLUS => $resolvedInnerExpr->type,
Expand Down Expand Up @@ -990,8 +1002,8 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
$kbCombinineWithAnd = $innerConditionLeft === AnalyserConditionTypeEnum::NOT_NULL;
}

$leftResult = $this->resolveExprType($expr->left, $innerConditionLeft);
$rightResult = $this->resolveExprType($expr->right, $innerConditionRight);
$leftResult = $this->resolveExprType($expr->left, $innerConditionLeft, $isNonEmptyAggResultSet);
$rightResult = $this->resolveExprType($expr->right, $innerConditionRight, $isNonEmptyAggResultSet);
$type = null;

if (
Expand Down Expand Up @@ -1116,6 +1128,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
case Expr\ExprTypeEnum::SUBQUERY:
assert($expr instanceof Expr\Subquery);
// TODO: handle $condition
// TODO: handle $isNonEmptyAggResultSet
$subqueryAnalyser = $this->getSubqueryAnalyser($expr->query);
$result = $subqueryAnalyser->analyse();
$canBeEmpty = ($result->rowCountRange->min ?? 0) === 0;
Expand Down Expand Up @@ -1188,7 +1201,10 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
assert($expr instanceof Expr\Between);
// TODO: handle $condition
$isNullable = array_reduce(
array_map($this->resolveExprType(...), [$expr->expression, $expr->min, $expr->max]),
array_map(
fn (Expr\Expr $e) => $this->resolveExprType($e, null, $isNonEmptyAggResultSet),
[$expr->expression, $expr->min, $expr->max],
),
static fn (bool $isNullable, ExprTypeResult $f) => $isNullable || $f->isNullable,
false,
);
Expand Down Expand Up @@ -1216,7 +1232,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
};
$kbCombinineWithAnd = $innerCondition === AnalyserConditionTypeEnum::NOT_NULL;
$innerFields = array_map(
fn (Expr\Expr $e) => $this->resolveExprType($e, $innerCondition),
fn (Expr\Expr $e) => $this->resolveExprType($e, $innerCondition, $isNonEmptyAggResultSet),
$expr->expressions,
);
$innerTypes = array_map(static fn (ExprTypeResult $f) => $f->type, $innerFields);
Expand Down Expand Up @@ -1261,8 +1277,8 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
AnalyserConditionTypeEnum::NULL => [AnalyserConditionTypeEnum::NULL, null],
null => [null, null],
};
$leftResult = $this->resolveExprType($expr->left, $innerConditionLeft);
$rightResult = $this->resolveExprType($expr->right, $innerConditionRight);
$leftResult = $this->resolveExprType($expr->left, $innerConditionLeft, $isNonEmptyAggResultSet);
$rightResult = $this->resolveExprType($expr->right, $innerConditionRight, $isNonEmptyAggResultSet);
$rightType = $rightResult->type;

// $rightType may not be a tuple if it's a subquery (e.g. "1 IN (SELECT 1)")
Expand Down Expand Up @@ -1305,8 +1321,8 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
case Expr\ExprTypeEnum::LIKE:
assert($expr instanceof Expr\Like);
// TODO: handle $condition
$expressionResult = $this->resolveExprType($expr->expression);
$patternResult = $this->resolveExprType($expr->pattern);
$expressionResult = $this->resolveExprType($expr->expression, null, $isNonEmptyAggResultSet);
$patternResult = $this->resolveExprType($expr->pattern, null, $isNonEmptyAggResultSet);
// TODO: check for valid escape char expressions.
// For example "ESCAPE IF(0, 'a', 'b')" seems to work, but "ESCAPE IF(id = id, 'a', 'b')" doesn't.
$escapeCharResult = $expr->escapeChar !== null
Expand Down Expand Up @@ -1379,7 +1395,11 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
foreach ($arguments as $arg) {
$innerCondition = $innerConditions[$position] ?? null;
$position++;
$resolvedArguments[] = $resolvedArg = $this->resolveExprType($arg, $innerCondition);
$resolvedArguments[] = $resolvedArg = $this->resolveExprType(
$arg,
$innerCondition,
$isNonEmptyAggResultSet,
);

if ($resolvedArg->type::getTypeEnum() === Schema\DbType\DbTypeEnum::TUPLE) {
$this->errors[] = AnalyserErrorBuilder::createInvalidFunctionArgumentError(
Expand All @@ -1398,7 +1418,12 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co

if ($functionInfo !== null) {
try {
return $functionInfo->getReturnType($expr, $resolvedArguments, $condition);
return $functionInfo->getReturnType(
$expr,
$resolvedArguments,
$condition,
$isNonEmptyAggResultSet,
);
} catch (AnalyserException $e) {
$this->errors[] = $e->toAnalyserError();
}
Expand All @@ -1413,7 +1438,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co

// TODO: handle $condition
if ($expr->compareValue) {
$field = $this->resolveExprType($expr->compareValue);
$field = $this->resolveExprType($expr->compareValue, null, $isNonEmptyAggResultSet);
$this->checkNotTuple($field->type);
}

Expand All @@ -1422,12 +1447,17 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
foreach ($expr->conditions as $caseCondition) {
$field = $this->resolveExprType($caseCondition->when);
$this->checkNotTuple($field->type);
$subresults[] = $field = $this->resolveExprType($caseCondition->then);
$subresults[] = $field = $this->resolveExprType(
$caseCondition->then,
null,
$isNonEmptyAggResultSet,
);
$this->checkNotTuple($field->type);
}

// TODO: CASE with no else may be nullable
if ($expr->else) {
$subresults[] = $field = $this->resolveExprType($expr->else);
$subresults[] = $field = $this->resolveExprType($expr->else, null, $isNonEmptyAggResultSet);
$this->checkNotTuple($field->type);
}

Expand All @@ -1446,6 +1476,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
case Expr\ExprTypeEnum::EXISTS:
assert($expr instanceof Expr\Exists);
// TODO: handle $condition
// TODO: handle $isNonEmptyAggResultSet
$this->getSubqueryAnalyser($expr->subquery)->analyse();

return new ExprTypeResult(
Expand All @@ -1455,6 +1486,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
case Expr\ExprTypeEnum::ASSIGNMENT:
assert($expr instanceof Expr\Assignment);
// TODO: handle $condition
// TODO: handle $isNonEmptyAggResultSet
$this->runWithDifferentFieldBehavior(
ColumnResolverFieldBehaviorEnum::ASSIGNMENT,
fn () => $this->resolveExprType($expr->target),
Expand All @@ -1469,7 +1501,7 @@ private function resolveExprType(Expr\Expr $expr, ?AnalyserConditionTypeEnum $co
);
case Expr\ExprTypeEnum::COLLATE:
assert($expr instanceof Expr\Collate);
$subresult = $this->resolveExprType($expr->expression, $condition);
$subresult = $this->resolveExprType($expr->expression, $condition, $isNonEmptyAggResultSet);

return new ExprTypeResult(
new Schema\DbType\VarcharType(),
Expand Down
3 changes: 2 additions & 1 deletion src/Database/FunctionInfo/Avg.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$arg = reset($argumentTypes);
assert($arg instanceof ExprTypeResult);
Expand All @@ -78,6 +79,6 @@ public function getReturnType(
default => new DecimalType(),
};

return new ExprTypeResult($type, true);
return new ExprTypeResult($type, ! $isNonEmptyAggResultSet || $arg->isNullable);
}
}
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Cast.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
assert($functionCall instanceof CastFunctionCall);
$exprType = $argumentTypes[0];
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/CeilFloor.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$value = $argumentTypes[0];

Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Coalesce.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$leftType = $argumentTypes[0]->type;
$isNullable = $argumentTypes[0]->isNullable;
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Concat.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$leftType = $this->concatOneType($argumentTypes[0]->type);
$isNullable = $argumentTypes[0]->isNullable;
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Count.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
return new ExprTypeResult(new IntType(), false);
}
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Curdate.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$kb = match ($condition) {
null => null,
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$date = $argumentTypes[0];
$isNullable = $date->isNullable || $date->type::getTypeEnum() !== DbTypeEnum::DATETIME;
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/DateAddSub.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$date = $argumentTypes[0];
$interval = $argumentTypes[1];
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/DateFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$date = $argumentTypes[0];
$format = $argumentTypes[1];
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/FoundRows.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
return new ExprTypeResult(new IntType(), false);
}
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/FunctionInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult;
}
9 changes: 8 additions & 1 deletion src/Database/FunctionInfo/GroupConcat.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
return new ExprTypeResult(new VarcharType(), true);
$isAnyArgNullable = false;

foreach ($argumentTypes as $argumentType) {
$isAnyArgNullable = $isAnyArgNullable || $argumentType->isNullable;
}

return new ExprTypeResult(new VarcharType(), ! $isNonEmptyAggResultSet || $isAnyArgNullable);
}
}
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/IfFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$then = $argumentTypes[1];
$else = $argumentTypes[2];
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/LowerUpper.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$arg = $argumentTypes[0];

Expand Down
3 changes: 2 additions & 1 deletion src/Database/FunctionInfo/MaxMin.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$arg = reset($argumentTypes);
assert($arg instanceof ExprTypeResult);

return new ExprTypeResult($arg->type, true);
return new ExprTypeResult($arg->type, ! $isNonEmptyAggResultSet || $arg->isNullable);
}
}
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Now.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$kb = match ($condition) {
null => null,
Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Round.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$valueType = $argumentTypes[0];
$digitsType = $argumentTypes[1] ?? null;
Expand Down
3 changes: 2 additions & 1 deletion src/Database/FunctionInfo/Sum.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$arg = reset($argumentTypes);
assert($arg instanceof ExprTypeResult);
Expand All @@ -78,6 +79,6 @@ public function getReturnType(
default => new DecimalType(),
};

return new ExprTypeResult($type, true);
return new ExprTypeResult($type, ! $isNonEmptyAggResultSet || $arg->isNullable);
}
}
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Trim.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$isNullable = false;

Expand Down
1 change: 1 addition & 0 deletions src/Database/FunctionInfo/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public function getReturnType(
FunctionCall $functionCall,
array $argumentTypes,
?AnalyserConditionTypeEnum $condition,
bool $isNonEmptyAggResultSet,
): ExprTypeResult {
$col = $argumentTypes[0];
// VALUE(...) can be used in SELECT as well, in which case it always returns null.
Expand Down
Loading

0 comments on commit 3080e3d

Please sign in to comment.