Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
6.0.x
8.0.x
9.0.x
10.0.x

- name: Set VERSION variable from tag
run: |
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
type: apache
- age_image: release_PG17_1.6.0
type: apache
- age_image: 16-bookworm-4
type: cnpg
- age_image: release_PG18_1.7.0
type: apache
- age_image: 16-1.5.0-standard-bookworm
type: cnpg
- age_image: 16-1.5.0-standard-trixie
Expand All @@ -31,6 +31,8 @@ jobs:
type: cnpg
- age_image: 17-1.6.0-standard-trixie
type: cnpg
- age_image: 18-1.7.0-standard-trixie
type: cnpg
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -95,9 +97,9 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
9.0.x
10.0.x

- name: Restore dependencies
run: dotnet restore
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/

# C# cache
*.lscache

# .NET Core
project.lock.json
project.fragment.lock.json
Expand Down
4 changes: 2 additions & 2 deletions src/Npgsql.Age/Npgsql.Age.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Npgsql" Version="10.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300" PrivateAssets="All" />
<PackageReference Include="Npgsql" Version="10.0.2" />
</ItemGroup>

</Project>
9 changes: 6 additions & 3 deletions src/Npgsql.Age/Npgsql.Age.csproj.lscache
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ Types/
<NUGET>/
microsoft.extensions.dependencyinjection.abstractions/10.0.0/lib/net10.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
microsoft.extensions.logging.abstractions/10.0.0/lib/net10.0/Microsoft.Extensions.Logging.Abstractions.dll
npgsql/10.0.0/lib/net10.0/Npgsql.dll
npgsql/10.0.2/lib/net10.0/Npgsql.dll
system.io.hashing/10.0.8/lib/net10.0/System.IO.Hashing.dll

[analyzerReferences]
<DOTNET>/packs/Microsoft.NETCore.App.Ref/10.0.6/analyzers/dotnet/cs/
Expand Down Expand Up @@ -509,8 +510,9 @@ Types/
<NUGET>/
microsoft.extensions.dependencyinjection.abstractions/8.0.0/lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
microsoft.extensions.logging.abstractions/8.0.0/lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll
npgsql/10.0.0/lib/net8.0/Npgsql.dll
npgsql/10.0.2/lib/net8.0/Npgsql.dll
system.diagnostics.diagnosticsource/9.0.11/lib/net8.0/System.Diagnostics.DiagnosticSource.dll
system.io.hashing/10.0.8/lib/net8.0/System.IO.Hashing.dll

[analyzerReferences]
<DOTNET>/packs/Microsoft.NETCore.App.Ref/8.0.26/analyzers/dotnet/cs/
Expand Down Expand Up @@ -770,7 +772,8 @@ Types/
<NUGET>/
microsoft.extensions.dependencyinjection.abstractions/9.0.0/lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll
microsoft.extensions.logging.abstractions/9.0.0/lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll
npgsql/10.0.0/lib/net9.0/Npgsql.dll
npgsql/10.0.2/lib/net9.0/Npgsql.dll
system.io.hashing/10.0.8/lib/net9.0/System.IO.Hashing.dll

[analyzerReferences]
<DOTNET>/packs/Microsoft.NETCore.App.Ref/9.0.15/analyzers/dotnet/cs/
Expand Down
238 changes: 231 additions & 7 deletions src/Npgsql.Age/Types/Agtype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,115 @@ public double GetDouble()
/// </returns>
public List<object?> GetList(bool readFloatingPointLiterals = true)
{
var result = JsonSerializer.Deserialize<List<object?>>(
_value,
SerializerOptions.Default
);

return result!;
var result = new List<object?>();
foreach (var element in GetArray())
{
if (element.IsNull)
{
result.Add(null);
}
else if (element.IsVertex)
{
result.Add(element.GetVertex());
}
else if (element.IsEdge)
{
result.Add(element.GetEdge());
}
else if (element.IsMap)
{
result.Add(element.GetMap());
}
else if (element.IsArray)
{
result.Add(element.GetList(readFloatingPointLiterals));
}
else if (element.IsJsonString)
{
var str = element.GetString();
if (readFloatingPointLiterals)
{
if (str.Equals("-Infinity", StringComparison.OrdinalIgnoreCase))
result.Add(double.NegativeInfinity);
else if (str.Equals("Infinity", StringComparison.OrdinalIgnoreCase))
result.Add(double.PositiveInfinity);
else if (str.Equals("NaN", StringComparison.OrdinalIgnoreCase))
result.Add(double.NaN);
else
result.Add(str);
}
else
{
result.Add(str);
}
}
else
{
var str = element.GetString();
if (readFloatingPointLiterals)
{
if (str.Equals("-Infinity", StringComparison.OrdinalIgnoreCase))
{
result.Add(double.NegativeInfinity);
continue;
}
if (str.Equals("Infinity", StringComparison.OrdinalIgnoreCase))
{
result.Add(double.PositiveInfinity);
continue;
}
if (str.Equals("NaN", StringComparison.OrdinalIgnoreCase))
{
result.Add(double.NaN);
continue;
}
}
else
{
if (
str.Equals("-Infinity", StringComparison.OrdinalIgnoreCase)
|| str.Equals("Infinity", StringComparison.OrdinalIgnoreCase)
|| str.Equals("NaN", StringComparison.OrdinalIgnoreCase)
)
{
result.Add(str);
continue;
}
}
if (
int.TryParse(
str,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out int intVal
)
)
result.Add(intVal);
else if (
long.TryParse(
str,
NumberStyles.Integer,
CultureInfo.InvariantCulture,
out long longVal
)
)
result.Add(longVal);
else if (
double.TryParse(
str,
NumberStyles.Float,
CultureInfo.InvariantCulture,
out double doubleVal
)
)
result.Add(doubleVal);
else if (bool.TryParse(str, out bool boolVal))
result.Add(boolVal);
else
result.Add(str);
}
}
return result;
}

/// <summary>
Expand Down Expand Up @@ -311,7 +414,9 @@ public Path GetPath()
SerializerOptions.PathSerializer
);

return path is null ? throw new Exception("Path cannot be null.") : new Path(path);
return path is null
? throw new FormatException("Path cannot be null.")
: new Path(path);
}
catch (JsonException e)
{
Expand All @@ -321,6 +426,122 @@ public Path GetPath()
);
}
}

/// <summary>
/// Returns <see langword="true"/> if the agtype represents a null value.
/// </summary>
public bool IsNull => _value == "null";

/// <summary>
/// Returns <see langword="true"/> if the agtype is an array.
/// </summary>
/// <remarks>
/// Paths (which also start with <c>[</c>) are not considered arrays because their
/// string representation ends with the <c>::path</c> footer rather than <c>]</c>.
/// </remarks>
public bool IsArray => _value.StartsWith('[') && _value.EndsWith(']');

/// <summary>
/// Returns <see langword="true"/> if the agtype is a plain JSON object (map).
/// </summary>
public bool IsMap => _value.StartsWith('{') && _value.EndsWith('}') && !IsVertex && !IsEdge;

/// <summary>
/// Returns <see langword="true"/> if the raw agtype value is a JSON string
/// (enclosed in double quotes).
/// </summary>
internal bool IsJsonString => _value.StartsWith('"') && _value.EndsWith('"');

/// <summary>
/// Returns the elements of the agtype array as individual <see cref="Agtype"/> values,
/// preserving type annotations so that <see cref="IsVertex"/>, <see cref="IsEdge"/>,
/// and other type-check properties work correctly on each element.
/// </summary>
/// <exception cref="FormatException">
/// Thrown when the agtype is not an array.
/// </exception>
public IEnumerable<Agtype> GetArray()
{
if (!IsArray)
throw new FormatException(
"Cannot convert agtype to array. Agtype is not a valid array."
);

return GetArrayCore();
}

private IEnumerable<Agtype> GetArrayCore()
{
// Walk the raw string tracking JSON nesting depth and string literals,
// splitting on top-level commas. This preserves ::vertex / ::edge suffixes
// on each element so the returned Agtype values keep their type information.
int depth = 0;
bool inString = false;
int start = 1; // skip opening '['
int end = _value.Length - 1; // position of closing ']'

for (int i = start; i < end; i++)
{
char c = _value[i];
if (inString)
{
if (c == '\\')
i++; // skip escaped character
else if (c == '"')
inString = false;
}
else
{
switch (c)
{
case '"':
inString = true;
break;
case '{':
case '[':
depth++;
break;
case '}':
case ']':
depth--;
break;
case ',' when depth == 0:
var item = _value.Substring(start, i - start).Trim();
if (item.Length > 0)
yield return new Agtype(item);
start = i + 1;
break;
}
}
}

// Yield the last (or only) item
if (end > start)
{
var lastItem = _value.Substring(start, end - start).Trim();
if (lastItem.Length > 0)
yield return new Agtype(lastItem);
}
}

/// <summary>
/// Returns the agtype map as a <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
/// <exception cref="FormatException">
/// Thrown when the agtype is not a map.
/// </exception>
public Dictionary<string, object?> GetMap()
{
if (!IsMap)
throw new FormatException(
"Cannot convert agtype to map. Agtype is not a valid map."
);

return JsonSerializer.Deserialize<Dictionary<string, object?>>(
_value,
SerializerOptions.Default
) ?? throw new FormatException("Cannot convert agtype to map.");
}
#endregion

#region Explicit operators
Expand Down Expand Up @@ -353,6 +574,9 @@ public Path GetPath()
public static explicit operator Vertex(Agtype agtype) => agtype.GetVertex();

public static explicit operator Edge(Agtype agtype) => agtype.GetEdge();

public static explicit operator Dictionary<string, object?>(Agtype agtype) =>
agtype.GetMap();
#endregion
}
}
Loading
Loading