Table of Contents

Complete result parsers

Reuse the single value wrapper parsers

public readonly record struct Maybe<T>(T? Value) : IWrapping<Maybe<T>, T> where T : class
{
    public static Maybe<T> Make(T value) => new(value);
}

var maybeMaker = new ReusingBaseTypeParserMaker(
    [typeof(Maybe<>)],
    (definition, itemType, ref _) => typeof(OptionalTypeParser<,>).MakeGenericType(definition.MakeGenericType(itemType), itemType),
    (definition, itemType, ref _) => typeof(FastOptionalTypeParser<,>).MakeGenericType(definition.MakeGenericType(itemType), itemType));

TypeParser.TypeParserMakers.Insert(0, maybeMaker);
Maybe<Album> album = GetAlbums.Query<Maybe<Album>>(cnn);

IWrapping<TSelf, T> provides the value construction used by the wrapper parsers.

Last value

public readonly record struct Last<T>(T Value);

public sealed class LastParser<T>(ITypeParser<T> inner) : BaseTypeParser<Last<T>>
{
    public override CommandBehavior Behavior => inner.Behavior & ~CommandBehavior.SingleRow;

    public override bool CanParse(ColumnInfo[] schema) => inner.CanParse(schema);

    public override Last<T> Default() => throw new RinkuNoRowsException();

    public override (bool CanContinue, Last<T> Result) Parse(DbDataReader reader)
    {
        (bool more, T value) = inner.Parse(reader);

        while (more)
            (more, value) = inner.Parse(reader);

        return (false, new Last<T>(value));
    }

    public override async ValueTask<(bool CanContinue, Last<T> Result)> ParseAsync(DbDataReader reader, CancellationToken ct = default)
    {
        (bool more, T value) = await inner.ParseAsync(reader, ct);

        while (more)
            (more, value) = await inner.ParseAsync(reader, ct);

        return (false, new Last<T>(value));
    }
}

Register the complete result parser maker during setup.

var lastParserMaker = new ReusingBaseTypeParserMaker(
    [typeof(Last<>)],
    (definition, itemType, ref _) => typeof(LastParser<>).MakeGenericType(itemType));

TypeParser.TypeParserMakers.Insert(0, lastParserMaker);
static readonly QueryCommand GetAlbums = new("SELECT AlbumId AS Id, Title FROM albums ORDER BY AlbumId");

Last<Album> last = GetAlbums.Query<Last<Album>>(cnn);
Album album = last.Value;

Album still uses its mapping. Last<T> changes how complete T values are consumed.

Schema compatibility

public override bool CanParse(ColumnInfo[] schema) => inner.CanParse(schema);

The cached parser reports which returned schemas it can parse.

Registration change after a parser was cached

GetAlbums.InvalidateParsers(QueryParserInvalidationScope.Global);

Cache control

Reader holding result parsers

Custom streamed results use IReaderHoldingParser<T>. Reader completion behavior is represented by IReaderDone.