Skip to content

Commit 3b7622c

Browse files
committed
type convertor fix
1 parent 10fe6d0 commit 3b7622c

File tree

11 files changed

+200
-59
lines changed

11 files changed

+200
-59
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
<RepositoryUrl>https://github.com/managedcode/graphrag</RepositoryUrl>
2727
<PackageProjectUrl>https://github.com/managedcode/graphrag</PackageProjectUrl>
2828
<Product>Managed Code GraphRag</Product>
29-
<Version>10.0.3</Version>
30-
<PackageVersion>10.0.3</PackageVersion>
29+
<Version>10.0.4</Version>
30+
<PackageVersion>10.0.4</PackageVersion>
3131

3232
</PropertyGroup>
3333
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">

Directory.Packages.props

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="2.0.0" />
1414
<PackageVersion Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="2.0.0" />
1515
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
16-
<PackageVersion Include="Gremlin.Net" Version="3.7.4" />
16+
<PackageVersion Include="Gremlin.Net" Version="3.8.0" />
1717
<PackageVersion Include="Neo4j.Driver" Version="5.28.3" />
18-
<PackageVersion Include="Npgsql" Version="9.0.4" />
18+
<PackageVersion Include="Npgsql" Version="10.0.0" />
1919
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.2.39" />
20-
<PackageVersion Include="Testcontainers.CosmosDb" Version="4.8.1" />
21-
<PackageVersion Include="Testcontainers.JanusGraph" Version="4.8.1" />
22-
<PackageVersion Include="Testcontainers.Neo4j" Version="4.8.1" />
23-
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.8.1" />
20+
<PackageVersion Include="Testcontainers.CosmosDb" Version="4.9.0" />
21+
<PackageVersion Include="Testcontainers.JanusGraph" Version="4.9.0" />
22+
<PackageVersion Include="Testcontainers.Neo4j" Version="4.9.0" />
23+
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.9.0" />
2424
<PackageVersion Include="xunit" Version="2.9.3" />
2525
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
2626
</ItemGroup>
27-
</Project>
27+
</Project>

src/ManagedCode.GraphRag.Postgres/ApacheAge/AgeConnectionManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ private AgeConnectionManager(PostgresGraphStoreOptions? options, string connecti
5353

5454
ConnectionString = connectionBuilder.ConnectionString;
5555
var dataSourceBuilder = new NpgsqlDataSourceBuilder(ConnectionString);
56+
dataSourceBuilder.UseAge();
5657
options?.ConfigureDataSourceBuilder?.Invoke(dataSourceBuilder);
5758
_dataSource = dataSourceBuilder.Build();
5859
_logger = logger ?? NullLogger<AgeConnectionManager>.Instance;

src/ManagedCode.GraphRag.Postgres/ApacheAge/Converters/AgtypeConverter.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44

55
namespace GraphRag.Storage.Postgres.ApacheAge.Converters;
66

7-
#pragma warning disable NPG9001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
87
internal sealed class AgtypeConverter : PgBufferedConverter<Agtype>
98
{
9+
public override Size GetSize(SizeContext context, Agtype value, ref object? writeState)
10+
{
11+
var byteCount = Encoding.UTF8.GetByteCount(value.GetString());
12+
return byteCount;
13+
}
14+
1015
public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements)
1116
{
1217
bufferRequirements = BufferRequirements.None;
1318
return format is DataFormat.Text;
1419
}
1520

16-
/// <summary>
17-
/// Read agtype from its binary representation.
18-
/// </summary>
19-
/// <param name="reader"></param>
20-
/// <returns></returns>
2121
protected override Agtype ReadCore(PgReader reader)
2222
{
2323
var textBytes = reader.ReadBytes(reader.CurrentRemaining);
@@ -26,16 +26,9 @@ protected override Agtype ReadCore(PgReader reader)
2626
return new(text);
2727
}
2828

29-
/// <summary>
30-
/// Write agtype to its binary representation.
31-
/// </summary>
32-
/// <param name="writer"></param>
33-
/// <param name="value"></param>
3429
protected override void WriteCore(PgWriter writer, Agtype value)
3530
{
3631
var bytes = Encoding.UTF8.GetBytes(value.GetString());
3732
writer.WriteBytes(bytes);
3833
}
3934
}
40-
#pragma warning restore NPG9001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
41-

src/ManagedCode.GraphRag.Postgres/ApacheAge/Extensions/NpgsqlExtensions.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ namespace Npgsql;
55

66
public static class NpgsqlExtensions
77
{
8-
/// <summary>
9-
/// Use Apache AGE types.
10-
/// </summary>
11-
/// <param name="mapper">Npgsql type mapper.</param>
12-
/// <returns></returns>
138
public static INpgsqlTypeMapper UseAge(this INpgsqlTypeMapper mapper)
149
{
1510
mapper.AddTypeInfoResolverFactory(new AgtypeResolverFactory());
1611
return mapper;
1712
}
13+
14+
public static NpgsqlDataSourceBuilder UseAge(this NpgsqlDataSourceBuilder builder)
15+
{
16+
builder.AddTypeInfoResolverFactory(new AgtypeResolverFactory());
17+
return builder;
18+
}
1819
}

src/ManagedCode.GraphRag.Postgres/ApacheAge/Resolvers/AgtypeResolverFactory.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,6 @@
55

66
namespace GraphRag.Storage.Postgres.ApacheAge.Resolvers;
77

8-
#pragma warning disable NPG9001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
9-
/// <summary>
10-
/// Factory class to resolve type information and specify which converters should be used
11-
/// to convert the PostgreSQL type 'ag_catalog.agtype' to our CLR <see cref="Agtype"/>.
12-
/// </summary>
13-
/// <remarks>
14-
/// <seealso href="https://medium.com/@dsylebee/nethereums-bigdecimal-support-for-npgsql-c21ec48897de">
15-
/// This article
16-
/// </seealso> was of great help.
17-
/// </remarks>
188
internal sealed class AgtypeResolverFactory : PgTypeInfoResolverFactory
199
{
2010
public override IPgTypeInfoResolver CreateResolver() => new Resolver();
@@ -59,4 +49,3 @@ private static TypeInfoMappingCollection AddMappings(TypeInfoMappingCollection m
5949
}
6050
}
6151
}
62-
#pragma warning restore NPG9001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

src/ManagedCode.GraphRag.Postgres/ManagedCode.GraphRag.Postgres.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
<ItemGroup>
1111
<ProjectReference Include="../ManagedCode.GraphRag/ManagedCode.GraphRag.csproj" />
1212
</ItemGroup>
13+
<PropertyGroup>
14+
<NoWarn>$(NoWarn);NPG9001</NoWarn>
15+
</PropertyGroup>
1316
<ItemGroup>
1417
<PackageReference Include="Npgsql" />
1518
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />

src/ManagedCode.GraphRag.Postgres/PostgresGraphStore.cs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
using GraphRag.Constants;
99
using GraphRag.Graphs;
1010
using GraphRag.Storage.Postgres.ApacheAge;
11+
using GraphRag.Storage.Postgres.ApacheAge.Types;
1112
using Microsoft.Extensions.DependencyInjection;
1213
using Microsoft.Extensions.Logging;
1314
using Microsoft.Extensions.Logging.Abstractions;
1415
using Npgsql;
15-
using NpgsqlTypes;
1616

1717
namespace GraphRag.Storage.Postgres;
1818

@@ -401,6 +401,10 @@ async IAsyncEnumerable<GraphRelationship> FetchAsync(string nodeId, [EnumeratorC
401401
await client.OpenConnectionAsync(token).ConfigureAwait(false);
402402

403403
await using var command = client.Connection.CreateCommand();
404+
var payload = JsonSerializer.Serialize(new Dictionary<string, object?>
405+
{
406+
[CypherParameterNames.NodeId] = nodeId
407+
});
404408
command.CommandText = string.Concat(
405409
"SELECT ",
406410
"\n source_id::text,",
@@ -410,13 +414,8 @@ async IAsyncEnumerable<GraphRelationship> FetchAsync(string nodeId, [EnumeratorC
410414
"\nFROM ag_catalog.cypher(", _graphNameLiteral, ", $$",
411415
"\n MATCH (source { id: $node_id })-[rel]->(target)",
412416
"\n RETURN source.id AS source_id, target.id AS target_id, type(rel) AS edge_type, properties(rel) AS edge_props",
413-
"\n$$, @params::ag_catalog.agtype) AS (source_id agtype, target_id agtype, edge_type agtype, edge_props agtype);");
414-
var payload = JsonSerializer.Serialize(new Dictionary<string, object?>
415-
{
416-
[CypherParameterNames.NodeId] = nodeId
417-
});
417+
"\n$$, @params) AS (source_id agtype, target_id agtype, edge_type agtype, edge_props agtype);");
418418
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, payload));
419-
420419
await using var reader = await command.ExecuteReaderAsync(token).ConfigureAwait(false);
421420
while (await reader.ReadAsync(token).ConfigureAwait(false))
422421
{
@@ -445,6 +444,7 @@ async IAsyncEnumerable<GraphRelationship> FetchRelationships(GraphTraversalOptio
445444

446445
await using var command = client.Connection.CreateCommand();
447446
var pagination = BuildPaginationClause(traversalOptions);
447+
var parametersJson = SerializeParameters(null);
448448
command.CommandText = string.Concat(
449449
"SELECT ",
450450
"\n source_id::text,",
@@ -456,9 +456,8 @@ async IAsyncEnumerable<GraphRelationship> FetchRelationships(GraphTraversalOptio
456456
"\n RETURN source.id AS source_id, target.id AS target_id, type(rel) AS edge_type, properties(rel) AS edge_props",
457457
"\n ORDER BY source.id, target.id, type(rel)",
458458
pagination,
459-
"\n$$, @params::ag_catalog.agtype) AS (source_id agtype, target_id agtype, edge_type agtype, edge_props agtype);");
460-
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, SerializeParameters(null)));
461-
459+
"\n$$, @params) AS (source_id agtype, target_id agtype, edge_type agtype, edge_props agtype);");
460+
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, parametersJson));
462461
await using var reader = await command.ExecuteReaderAsync(token).ConfigureAwait(false);
463462
while (await reader.ReadAsync(token).ConfigureAwait(false))
464463
{
@@ -487,6 +486,7 @@ async IAsyncEnumerable<GraphNode> FetchNodes(GraphTraversalOptions? traversalOpt
487486

488487
await using var command = client.Connection.CreateCommand();
489488
var pagination = BuildPaginationClause(traversalOptions);
489+
var parametersJson = SerializeParameters(null);
490490
command.CommandText = string.Concat(
491491
"SELECT ",
492492
"\n node_label::text,",
@@ -497,9 +497,8 @@ async IAsyncEnumerable<GraphNode> FetchNodes(GraphTraversalOptions? traversalOpt
497497
"\n RETURN head(labels(n)) AS node_label, n.id AS node_id, properties(n) AS node_props",
498498
"\n ORDER BY n.id",
499499
pagination,
500-
"\n$$, @params::ag_catalog.agtype) AS (node_label agtype, node_id agtype, node_props agtype);");
501-
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, SerializeParameters(null)));
502-
500+
"\n$$, @params) AS (node_label agtype, node_id agtype, node_props agtype);");
501+
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, parametersJson));
503502
await using var reader = await command.ExecuteReaderAsync(token).ConfigureAwait(false);
504503
while (await reader.ReadAsync(token).ConfigureAwait(false))
505504
{
@@ -525,17 +524,16 @@ protected virtual async Task ExecuteCypherAsync(string query, IReadOnlyDictionar
525524
private async Task ExecuteCypherAsync(IAgeClient client, string query, IReadOnlyDictionary<string, object?> parameters, CancellationToken cancellationToken)
526525
{
527526
var queryLiteral = WrapInDollarQuotes(query);
527+
var payload = SerializeParameters(parameters);
528528
var commandText = string.Concat(
529529
"SELECT *",
530-
"\nFROM ag_catalog.cypher(", _graphNameLiteral, ", ", queryLiteral, "::cstring, @params::ag_catalog.agtype) AS (result agtype);");
531-
var payload = SerializeParameters(parameters);
530+
"\nFROM ag_catalog.cypher(", _graphNameLiteral, ", ", queryLiteral, "::cstring, @params) AS (result agtype);");
532531

533532
for (var attempt = 0; attempt < 2; attempt++)
534533
{
535534
await using var command = client.Connection.CreateCommand();
536535
command.CommandText = commandText;
537536
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, payload));
538-
539537
try
540538
{
541539
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
@@ -856,7 +854,7 @@ protected virtual async Task<IReadOnlyList<string>> ExecuteExplainAsync(string e
856854
var explainLiteral = WrapInDollarQuotes(explainQuery);
857855
command.CommandText = string.Concat(
858856
"SELECT plan",
859-
"\nFROM ag_catalog.cypher(", _graphNameLiteral, ", ", explainLiteral, "::cstring, @params::ag_catalog.agtype) AS (plan text);");
857+
"\nFROM ag_catalog.cypher(", _graphNameLiteral, ", ", explainLiteral, "::cstring, @params) AS (plan text);");
860858
command.Parameters.Add(CreateAgTypeParameter(CypherParameterNames.Parameters, parameterJson));
861859

862860
var plan = new List<string>();
@@ -892,10 +890,10 @@ private static NpgsqlParameter CreateAgTypeParameter(string name, string jsonPay
892890
{
893891
ArgumentNullException.ThrowIfNull(jsonPayload);
894892

895-
return new NpgsqlParameter(name, NpgsqlDbType.Unknown)
893+
var agtype = new Agtype(jsonPayload);
894+
return new NpgsqlParameter<Agtype>(name, agtype)
896895
{
897-
DataTypeName = "ag_catalog.agtype",
898-
Value = jsonPayload
896+
DataTypeName = "ag_catalog.agtype"
899897
};
900898
}
901899

tests/ManagedCode.GraphRag.Tests/GraphRagApplicationFixture.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ public async Task InitializeAsync()
6969
.WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(5432))
7070
.Build();
7171

72-
_janusContainer = new JanusGraphBuilder().Build();
72+
_janusContainer = new JanusGraphBuilder()
73+
.WithWaitStrategy(Wait.ForUnixContainer().UntilInternalTcpPortIsAvailable(8182))
74+
.Build();
7375

7476
if (IsCosmosSupported())
7577
{

tests/ManagedCode.GraphRag.Tests/Integration/GraphStoreIntegrationTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,35 @@ public async Task GraphStores_HandlePagination(string providerKey)
246246
Assert.Equal(2, pagedEdges.Count);
247247
}
248248

249+
[Theory]
250+
[MemberData(nameof(GraphProviders))]
251+
public async Task PostgresGraphStore_PersistsStringPropertiesWithAgtypeParameters(string providerKey)
252+
{
253+
if (!string.Equals(providerKey, "postgres", StringComparison.OrdinalIgnoreCase))
254+
{
255+
// Other providers do not use agtype parameters.
256+
return;
257+
}
258+
259+
var store = GetStore(providerKey);
260+
Assert.NotNull(store);
261+
await store!.InitializeAsync();
262+
263+
var label = GraphStoreTestProviders.GetLabel(providerKey);
264+
var nodeId = $"{providerKey}-agtype-{Guid.NewGuid():N}";
265+
var payload = "line1\nline2 \"quoted\" \\ backslash and {braces}";
266+
267+
await store.UpsertNodeAsync(nodeId, label, new Dictionary<string, object?>
268+
{
269+
["content"] = payload,
270+
["note"] = "ensure-agtype-parameter"
271+
});
272+
273+
var stored = await FindNodeAsync(store, nodeId);
274+
Assert.NotNull(stored);
275+
Assert.Equal(payload, stored!.Properties["content"]?.ToString());
276+
}
277+
249278
[Fact]
250279
[Trait("Category", "Cosmos")]
251280
public async Task CosmosGraphStore_RoundTrips_WhenEmulatorAvailable()

0 commit comments

Comments
 (0)