Canton.Ledger.Pqs.Client
0.7.1
Prefix Reserved
dotnet add package Canton.Ledger.Pqs.Client --version 0.7.1
NuGet\Install-Package Canton.Ledger.Pqs.Client -Version 0.7.1
<PackageReference Include="Canton.Ledger.Pqs.Client" Version="0.7.1" />
<PackageVersion Include="Canton.Ledger.Pqs.Client" Version="0.7.1" />
<PackageReference Include="Canton.Ledger.Pqs.Client" />
paket add Canton.Ledger.Pqs.Client --version 0.7.1
#r "nuget: Canton.Ledger.Pqs.Client, 0.7.1"
#:package Canton.Ledger.Pqs.Client@0.7.1
#addin nuget:?package=Canton.Ledger.Pqs.Client&version=0.7.1
#tool nuget:?package=Canton.Ledger.Pqs.Client&version=0.7.1
Canton.Ledger.Pqs.Client
Type-safe query client for the Canton Participant Query Store (PQS), part of the Canton .NET SDK. Provides expression-based, SQL-injection-safe queries over active Daml contracts via PostgreSQL.
Overview
PQS exposes the ledger state as a PostgreSQL database. This package provides a strongly-typed client that queries active contracts using the generated Daml C# bindings. Filter field names are derived from expressions against those bindings, and values are always parameterized — eliminating SQL injection by construction.
The query surface itself — IPqsClient, the Filter/PqsFilter DSL, PqsPage and InterfaceContract<TInterface, TView> — is declared in Canton.Ledger.Abstractions, so code written against IPqsClient (and the FakePqsClient in Canton.Ledger.Testing) needs no PostgreSQL dependency. This package supplies PqsClient, the Npgsql-backed implementation, plus its options, health check and DI wiring.
Filter builds a transport-neutral predicate tree in Canton.Ledger.Abstractions; this package renders it to parameterized SQL. FakePqsClient evaluates the same tree in memory. Upgrade Canton.Ledger.Pqs.Client and Canton.Ledger.Abstractions together: an older Canton.Ledger.Pqs.Client next to a newer Canton.Ledger.Abstractions raises MissingMethodException on its first filtered query.
Installation
dotnet add package Canton.Ledger.Pqs.Client --version 0.7.1
Usage
Basic Setup
The client is entered through dependency injection: register it on an IServiceCollection, then
resolve IPqsClient. The container binds and validates PqsClientOptions at startup and picks up a
registered NpgsqlDataSource when one is present.
using Canton.Ledger.Abstractions;
using Canton.Ledger.Pqs.Client;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddPqsClient(options =>
options.ConnectionString = "Host=localhost;Database=pqs;Username=pqs;Password=pqs");
await using var provider = services.BuildServiceProvider();
var pqsClient = provider.GetRequiredService<IPqsClient>();
Querying All Active Contracts
// Using generated template types from Daml.Codegen.CSharp
var agreements = await pqsClient.QueryAsync<Agreement>();
Querying with Filters
// Single field equality
var active = await pqsClient.QueryAsync<Agreement>(
Filter.Field<Agreement>(a => a.Status, "Active"));
// OR condition
var myAgreements = await pqsClient.QueryAsync<Agreement>(
Filter.Or(
Filter.Field<Agreement>(a => a.Initiator, partyId),
Filter.Field<Agreement>(a => a.Counterparty, partyId)));
// AND condition
var myActive = await pqsClient.QueryAsync<Agreement>(
Filter.And(
Filter.Field<Agreement>(a => a.Status, "Active"),
Filter.Or(
Filter.Field<Agreement>(a => a.Initiator, partyId),
Filter.Field<Agreement>(a => a.Counterparty, partyId))));
Filter.Field parses its value as the field's Daml type, so "42.5" matches a Numeric stored as
42.5000000000, and its selector may reach into nested records (a => a.Terms.Currency).
Filter.Where translates a C# predicate over the generated bindings into parameterized SQL, typed by
each field's Daml type. It supports comparisons (<, >=, … on Int64, Numeric, Date and Time),
nested records, Optional, List, Map/TextMap and variant navigation, and && / || / !:
var large = await pqsClient.QueryAsync<Agreement>(
Filter.Where<Agreement>(a =>
a.Amount >= 1000m
&& a.Terms.Expiry < cutoff
&& a.Note != null
&& a.Tags.Contains("priority")
&& a.Limits["daily"] > 10
&& a.State is AgreementState.Accepted));
== and != also work on an Optional enum field (o => o.MaybeSide == Side.Buy), where != still matches a row whose field is None.
A predicate that reaches a member of a type the code generator did not emit, such as string.Length, a collection's Count or Party.Value, throws ArgumentException ("Unsupported expression in a PQS filter"), like every other unsupported construct.
Fetching a Single Contract
// By filter (returns first match or null)
var contract = await pqsClient.QueryOneAsync<Agreement>(
Filter.Field<Agreement>(a => a.Initiator, partyId));
// By contract ID
var byId = await pqsClient.FetchByIdAsync<Agreement>(contractId);
// Check existence
var exists = await pqsClient.ExistsAsync<Agreement>(contractId);
Dependency Injection
The recommended DI lifetime is Singleton — PqsClient holds only configuration state and relies on Npgsql's built-in connection pooling for database connections.
// Using the extension method (recommended)
services.AddPqsClient(configuration.GetSection("Canton:Pqs"));
// Or using an action delegate
services.AddPqsClient(options =>
{
options.ConnectionString = "Host=localhost;Database=pqs";
});
// Health check (uses the configured connection string)
services.AddHealthChecks().AddPqsClient(tags: ["database", "ready"]);
OpenTelemetry Tracing
Canton.Ledger.OpenTelemetry registers every Canton client source at once:
tracing.AddCantonLedgerInstrumentation();
To register this source by hand, take the name from Canton.Ledger.Kernel — no reference to this package required:
tracing.AddSource(LedgerActivitySourceNames.PqsClient);
Payload Decoding
Each row's payload is decoded with the generated Daml-LF JSON reader, so Daml variants, optionals and numerics need no serializer configuration, and the payload-returning methods require a generated template type (where T : ITemplate, IDamlRecord<T>).
Decoding is strict:
- PQS documents Daml
NumericandInt64as JSON strings by default, and--target-encoding-numericasstring/--target-encoding-int64asstringchange that default. A bare JSON number where anInt64orNumericis expected is refused. - PQS stores nullable fields as JSON nulls by default. A payload that omits the key of a Daml
Optionalfield, as--target-encoding-excludenullsdoes, decodes that field asNone; a payload that omits any other field's key fails to decode with an error naming the field. - A row over 16 MiB, 100,000 JSON nodes or nesting depth 128 fails to decode. These limits are not configurable.
A row that cannot be decoded throws JsonException.
Related Packages
Canton.Ledger.Abstractions- declaresIPqsClient,Filter/PqsFilter,PqsPageandInterfaceContract<TInterface, TView>Canton.Ledger.Testing-FakePqsClient, an in-memoryIPqsClientfor unit testsCanton.Ledger.Grpc.Client- gRPC client for command submissionDaml.Runtime- Runtime types for generated Daml contractsDaml.Codegen.CSharp- Code generator for Daml contracts
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Canton.Ledger.Abstractions (>= 0.7.1)
- Canton.Ledger.Kernel (>= 0.7.1)
- Daml.Runtime (>= 0.7.1)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.13)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.13)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.13)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.13)
- Microsoft.Extensions.Options (>= 10.0.13)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.13)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.13)
- Npgsql (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.1 | 61 | 10/23/2026 |
| 0.7.0 | 193 | 10/14/2026 |
| 0.7.0-preview.1 | 402 | 10/13/2026 |
| 0.6.0 | 278 | 10/7/2026 |
| 0.6.0-preview.4 | 164 | 10/7/2026 |
| 0.6.0-preview.3 | 187 | 10/1/2026 |
| 0.6.0-preview.2 | 413 | 9/30/2026 |
| 0.6.0-preview.1 | 214 | 9/28/2026 |
| 0.5.0-preview.3 | 235 | 9/24/2026 |
| 0.5.0-preview.2 | 150 | 9/22/2026 |
| 0.5.0-preview.1 | 230 | 9/8/2026 |
| 0.4.1-preview.1 | 95 | 8/29/2026 |
| 0.4.0-preview.1 | 188 | 7/18/2026 |
| 0.2.0-preview.1 | 120 | 7/6/2026 |
| 0.1.5-preview.1 | 95 | 6/25/2026 |
| 0.1.4-preview.1 | 86 | 6/15/2026 |