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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Canton.Ledger.Pqs.Client" Version="0.7.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Canton.Ledger.Pqs.Client" Version="0.7.1" />
                    
Directory.Packages.props
<PackageReference Include="Canton.Ledger.Pqs.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Canton.Ledger.Pqs.Client --version 0.7.1
                    
#r "nuget: Canton.Ledger.Pqs.Client, 0.7.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Canton.Ledger.Pqs.Client@0.7.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Canton.Ledger.Pqs.Client&version=0.7.1
                    
Install as a Cake Addin
#tool nuget:?package=Canton.Ledger.Pqs.Client&version=0.7.1
                    
Install as a Cake Tool

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 Numeric and Int64 as JSON strings by default, and --target-encoding-numericasstring / --target-encoding-int64asstring change that default. A bare JSON number where an Int64 or Numeric is expected is refused.
  • PQS stores nullable fields as JSON nulls by default. A payload that omits the key of a Daml Optional field, as --target-encoding-excludenulls does, decodes that field as None; 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.

  • Canton.Ledger.Abstractions - declares IPqsClient, Filter/PqsFilter, PqsPage and InterfaceContract<TInterface, TView>
  • Canton.Ledger.Testing - FakePqsClient, an in-memory IPqsClient for unit tests
  • Canton.Ledger.Grpc.Client - gRPC client for command submission
  • Daml.Runtime - Runtime types for generated Daml contracts
  • Daml.Codegen.CSharp - Code generator for Daml contracts
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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