/*
* Copyright (c) 2022 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Data
* File: EnumerableTable.cs
*
* EnumerableTable.cs is part of VNLib.Plugins.Extensions.Data which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Extensions.Data is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Plugins.Extensions.Data is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
using System;
using System.Data;
using System.Threading;
using System.Data.Common;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace VNLib.Plugins.Extensions.Data.SQL
{
///
/// A base class for client side async enumerable SQL queries
///
/// The entity type
public abstract class EnumerableTable : TableManager, IAsyncEnumerable
{
const string DEFAULT_ENUM_STATMENT = "SELECT *\r\nFROM @table\r\n;";
public EnumerableTable(Func factory, string tableName) : base(factory, tableName)
{
//Build the default select all statment
Enumerate = DEFAULT_ENUM_STATMENT.Replace("@table", tableName);
}
public EnumerableTable(Func factory) : base(factory)
{ }
///
/// The command that will be run against the database to return rows for enumeration
///
protected string Enumerate { get; set; }
///
/// The isolation level to use when creating the transaction during enumerations
///
protected IsolationLevel TransactionIsolationLevel { get; set; } = IsolationLevel.ReadUncommitted;
IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
{
return GetAsyncEnumerator(cancellationToken: cancellationToken);
}
///
/// Transforms a row from the into the item type
/// to be returned when yielded.
///
/// The reader to get the item data from
/// A token to cancel the operation
/// A task that returns the transformed item
/// The position is set before this method is invoked
protected abstract Task GetItemAsync(DbDataReader reader, CancellationToken cancellationToken);
///
/// Invoked when an item is no longer in the enumerator scope, in the enumeration process.
///
/// The item to cleanup
/// A token to cancel the operation
/// A ValueTask that represents the cleanup process
protected abstract ValueTask CleanupItemAsync(T item, CancellationToken cancellationToken);
///
/// Gets an to enumerate items within the backing store.
///
/// Cleanup items after each item is enumerated and the enumeration scope has
/// returned to the enumerator
/// A token to cancel the enumeration
/// A to enumerate records within the store
public virtual async IAsyncEnumerator GetAsyncEnumerator(bool closeItems = true, CancellationToken cancellationToken = default)
{
await using DbConnection db = GetConnection();
await db.OpenAsync(cancellationToken);
await using DbTransaction transaction = await db.BeginTransactionAsync(cancellationToken);
//Start the enumeration command
await using DbCommand cmd = db.CreateTextCommand(Enumerate, transaction);
await cmd.PrepareAsync(cancellationToken);
await using DbDataReader reader = await cmd.ExecuteReaderAsync(cancellationToken);
//loop through results and transform each element
while (reader.Read())
{
//get the item
T item = await GetItemAsync(reader, cancellationToken);
try
{
yield return item;
}
finally
{
if (closeItems)
{
//Cleanup the item
await CleanupItemAsync(item, cancellationToken);
}
}
}
}
}
}