/* * 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 General Public License as published * by the Free Software Foundation, either version 2 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VNLib.Plugins.Extensions.Data. If not, see http://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); } } } } } }