Skip to content

Commit

Permalink
Fix scalar casting from string for POCO/DOM JSON mode (npgsql#3340)
Browse files Browse the repository at this point in the history
  • Loading branch information
roji authored and WhatzGames committed Dec 18, 2024
1 parent c217da3 commit 5553bee
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,11 @@ public NpgsqlJsonDomTranslator(
typeof(string),
_stringTypeMapping);

// The PostgreSQL traversal operator always returns text - for these scalar-returning methods, apply a conversion from string.
return method.Name == nameof(JsonElement.GetString)
? traversalToText
: ConvertFromText(traversalToText, method.ReturnType);
: _sqlExpressionFactory.Convert(
traversalToText, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model));
}

if (method == GetArrayLength)
Expand All @@ -152,30 +154,4 @@ public NpgsqlJsonDomTranslator(

return null;
}

// The PostgreSQL traversal operator always returns text, so we need to convert to int, bool, etc.
private SqlExpression ConvertFromText(SqlExpression expression, Type returnType)
{
switch (Type.GetTypeCode(returnType))
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.DateTime:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.SByte:
case TypeCode.Single:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model));
default:
return returnType == typeof(Guid)
? _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model))
: expression;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public NpgsqlJsonPocoTranslator(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression? TranslateMemberAccess(SqlExpression instance, SqlExpression member, Type returnType)
=> instance switch
{
return instance switch
{
// The first time we see a JSON traversal it's on a column - create a JsonTraversalExpression.
// Traversals on top of that get appended into the same expression.
Expand All @@ -119,6 +120,25 @@ PgJsonTraversalExpression prevPathTraversal
_ => null
};

// The PostgreSQL traversal operator always returns text.
// If the type returned is a scalar (int, bool, etc.), we need to apply a conversion from string.
SqlExpression ConvertFromText(SqlExpression expression, Type returnType)
=> _typeMappingSource.FindMapping(returnType.UnwrapNullableType(), _model) switch
{
// Type mapping not found - this isn't a scalar
null => expression,

// Arrays are dealt with as JSON arrays, not array scalars
NpgsqlArrayTypeMapping => expression,

// Text types don't a a conversion to string
{ StoreTypeNameBase: "text" or "varchar" or "char" } => expression,

// For any other type mapping, this is a scalar; apply a conversion to the type from string.
var mapping => _sqlExpressionFactory.Convert(expression, returnType, mapping)
};
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -159,38 +179,4 @@ PgJsonTraversalExpression prevPathTraversal
return null;
}
}

// The PostgreSQL traversal operator always returns text, so we need to convert to int, bool, etc.
private SqlExpression ConvertFromText(SqlExpression expression, Type returnType)
{
var unwrappedReturnType = returnType.UnwrapNullableType();

switch (Type.GetTypeCode(unwrappedReturnType))
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.DateTime:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.SByte:
case TypeCode.Single:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model));
}

if (unwrappedReturnType == typeof(Guid)
|| unwrappedReturnType == typeof(DateTimeOffset)
|| unwrappedReturnType == typeof(DateOnly)
|| unwrappedReturnType == typeof(TimeOnly))
{
return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model));
}

return expression;
}
}

0 comments on commit 5553bee

Please sign in to comment.