Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,33 @@ private Expression VisitInlinedParameterExpression(MySqlInlinedParameterExpressi

protected virtual Expression VisitJsonPathTraversal(MySqlJsonTraversalExpression expression)
{
// If the path contains parameters, then the -> and ->> aliases are not supported by MySQL, because
// we need to concatenate the path and the parameters.
// We will use JSON_EXTRACT (and JSON_UNQUOTE if needed) only in this case, because the aliases
// are much more readable.
var isSimplePath = expression.Path.All(
l => l is SqlConstantExpression ||
l is MySqlJsonArrayIndexExpression e && e.Expression is SqlConstantExpression);
var isScalar = expression.Type.IsPrimitive();
var useJsonValue = _options.ServerVersion.Supports.JsonValue &&
isScalar;

if (useJsonValue)
{
VisitJsonPathTraversalCore(expression, "JSON_VALUE");
return expression;
}

// JSON_EXTRACT() returns the SQL string `null` for a JSON value of `null`, instead of a SQL value of `NULL`.
// This is an unfortunate oversight by the MySQL team, that is unlikely to be corrected (see
// https://bugs.mysql.com/bug.php?id=85755).
// While JSON_VALUE() handles this scenario correctly for scalar values, for MySQL versions without JSON_VALUE()
// support, we need to explicitly ensure that the returned value is not `null`, or we could return a string
// containing `null` independent of what is expected.

Sql.Append("CASE ");

VisitJsonPathTraversalCore(
expression,
"JSON_CONTAINS",
new SqlConstantExpression(
Expression.Constant("null"),
_typeMappingSource.GetMapping(typeof(string))));

Sql.Append(" WHEN TRUE THEN NULL ELSE ");

if (expression.ReturnsText)
{
Expand All @@ -121,67 +141,100 @@ protected virtual Expression VisitJsonPathTraversal(MySqlJsonTraversalExpression

if (expression.Path.Count > 0)
{
Sql.Append("JSON_EXTRACT(");
VisitJsonPathTraversalCore(expression, "JSON_EXTRACT");
}

if (expression.ReturnsText)
{
Sql.Append(")");
}

Sql.Append(" END");

return expression;
}

protected virtual Expression VisitJsonPathTraversalCore(
MySqlJsonTraversalExpression expression,
string functionName,
SqlExpression secondArgumentExpression = null)
{
Sql.Append(functionName)
.Append("(");

Visit(expression.Expression);

if (secondArgumentExpression is not null)
{
Sql.Append(", ");

Visit(secondArgumentExpression);
}

if (expression.Path.Count > 0)
{
Sql.Append(", ");

if (!isSimplePath)
{
Sql.Append("CONCAT(");
}
GenerateJsonPathExpression(expression.Path);
}

Sql.Append("'$");
Sql.Append(")");

foreach (var location in expression.Path)
{
if (location is MySqlJsonArrayIndexExpression arrayIndexExpression)
{
var isConstantExpression = arrayIndexExpression.Expression is SqlConstantExpression;
return expression;
}

Sql.Append("[");
private void GenerateJsonPathExpression(IReadOnlyList<SqlExpression> path)
{
// If the path contains parameters, then the -> and ->> aliases are not supported by MySQL, because
// we need to concatenate the path and the parameters.
// We will use JSON_EXTRACT (and JSON_UNQUOTE if needed) only in this case, because the aliases
// are much more readable.
var isSimplePath = path.All(
l => l is SqlConstantExpression ||
l is MySqlJsonArrayIndexExpression e && e.Expression is SqlConstantExpression);

if (!isConstantExpression)
{
Sql.Append("', ");
}
if (!isSimplePath)
{
Sql.Append("CONCAT(");
}

Visit(arrayIndexExpression.Expression);
Sql.Append("'$");

if (!isConstantExpression)
{
Sql.Append(", '");
}
foreach (var location in path)
{
if (location is MySqlJsonArrayIndexExpression arrayIndexExpression)
{
var isConstantExpression = arrayIndexExpression.Expression is SqlConstantExpression;

Sql.Append("]");
}
else
Sql.Append("[");

if (!isConstantExpression)
{
Sql.Append(".");
Visit(location);
Sql.Append("', ");
}
}

Sql.Append("'");
Visit(arrayIndexExpression.Expression);

if (!isConstantExpression)
{
Sql.Append(", '");
}

if (!isSimplePath)
Sql.Append("]");
}
else
{
Sql.Append(")");
Sql.Append(".");
Visit(location);
}

Sql.Append(")");
}

if (expression.ReturnsText)
Sql.Append("'");

if (!isSimplePath)
{
Sql.Append(")");
}

return expression;
}

protected override Expression VisitColumn(ColumnExpression columnExpression)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public MySqlJsonTraversalExpression(
bool returnsText,
[NotNull] Type type,
[CanBeNull] RelationalTypeMapping typeMapping)
: this(expression, new SqlExpression[0], returnsText, type, typeMapping)
: this(expression, new SqlExpression[0], returnsText, type.UnwrapNullableType(), typeMapping)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ await AssertQueryScalar(

AssertSql(
$"""
SELECT LOG(7.0, CAST(`o`.`Discount` AS double))
SELECT LOG(7.0, {MySqlTestHelpers.CastAsDouble("`o`.`Discount`")})
FROM `Order Details` AS `o`
WHERE ((`o`.`OrderID` = 11077) AND (`o`.`Discount` > 0)) AND (LOG(7.0, {MySqlTestHelpers.CastAsDouble("`o`.`Discount`")}) < -1.0)
""");
Expand Down