diff --git a/src/Gemstone.Data/Model/ClaimQueryRestrictionAttribute.cs b/src/Gemstone.Data/Model/ClaimQueryRestrictionAttribute.cs new file mode 100644 index 000000000..e755774fe --- /dev/null +++ b/src/Gemstone.Data/Model/ClaimQueryRestrictionAttribute.cs @@ -0,0 +1,85 @@ +//****************************************************************************************************** +// ClaimQueryRestrictionAttribute.cs - Gbtc +// +// Copyright © 2026, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may +// not use this file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 01/09/2026 - J. Ritchie Carroll +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Data; + +namespace Gemstone.Data.Model; + +/// +/// Defines an attribute that will mark a modeled table with a record restriction that applies +/// to secure query functions for a modeled . +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class ClaimQueryRestrictionAttribute : Attribute +{ + /// + /// Defines filter SQL expression for restriction as a composite format string - does not + /// include WHERE. When escaping is needed for field names, use standard ANSI quotes. + /// + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format string will be converted into + /// query parameters where each of the corresponding values in the + /// collection will be applied as values to an executed + /// query. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + public readonly string FilterExpression; + + /// + /// Defines claims which will be checked for parameter values + /// + public readonly string[] Claims; + + /// + /// Creates a new parameterized with the specified + /// SQL filter expression and parameters. + /// + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Claims to use for parameter values. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will retrieved and applied as + /// values to an executed query. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + public ClaimQueryRestrictionAttribute(string filterExpression, params string[] claims) + { + FilterExpression = filterExpression ?? throw new ArgumentNullException(nameof(filterExpression)); + Claims = claims ?? throw new ArgumentNullException(nameof(claims)); + } +} diff --git a/src/Gemstone.Data/Model/SecureTableOperations.cs b/src/Gemstone.Data/Model/SecureTableOperations.cs new file mode 100644 index 000000000..d1e887b51 --- /dev/null +++ b/src/Gemstone.Data/Model/SecureTableOperations.cs @@ -0,0 +1,1164 @@ +//****************************************************************************************************** +// SecureTableOperations.cs - Gbtc +// +// Copyright © 2016, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 02/01/2016 - G. Santos +// Generated original version of source code. +// +//****************************************************************************************************** + +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Gemstone.Expressions.Model; +using Gemstone.Reflection.MemberInfoExtensions; + +namespace Gemstone.Data.Model; + +/// +/// A wrapper class for +/// +/// Modeled table. +public class SecureTableOperations where T : class, new() +{ + /// + /// which performs DB operations. + /// + public TableOperations BaseOperations { get; } + + /// + /// Creates a new + /// + /// table operation which to wrap calls of. + public SecureTableOperations(TableOperations operations) + { + BaseOperations = operations; + } + + /// + /// Creates a new + /// + /// db to create secure operations to. + public SecureTableOperations(AdoDataConnection connection) + { + BaseOperations = new(connection); + } + + #region [ Methods ] + + /// + /// Transforms a into an equivalent , as defined by the model's . + /// + /// Claims principal which is making the request. + /// + /// + private static RecordRestriction? GetClaimRecordRestriction(ClaimsPrincipal principal) + { + if (s_claimQueryRestrictionAttributes is null) + return null; + + RecordRestriction? completeRestriction = null; + foreach(ClaimQueryRestrictionAttribute attribute in s_claimQueryRestrictionAttributes) + { + object[] claimValues = attribute.Claims + .Select(claimKey => principal.FindFirst(claimKey) ?? throw new InvalidOperationException($"Unable to retrieve {claimKey} claim from user.")) + .Select(claim => claim.Value) + .ToArray(); + completeRestriction += new RecordRestriction(attribute.FilterExpression, claimValues); + } + + return completeRestriction; + } + + /// + /// Queries database and returns a single modeled table record for the specified . + /// + /// Claims principal which is making the request. + /// Record restriction to apply. + /// A single modeled table record for the queried record. + /// + /// + /// If no record is found for specified , null will be returned. + /// + /// + /// This is a convenience call to + /// specifying the parameter with a limit of 1 record. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public T? QueryRecord(ClaimsPrincipal principal, RecordRestriction? restriction) => + BaseOperations.QueryRecord(restriction + GetClaimRecordRestriction(principal)); + + /// + /// Queries database and returns a single modeled table record for the specified . + /// + /// Claims principal which is making the request. + /// Record restriction to apply. + /// Propagates notification that operations should be canceled. + /// A single modeled table record for the queried record. + /// + /// + /// If no record is found for specified , null will be returned. + /// + /// + /// This is a convenience call to + /// specifying the parameter with a limit of 1 record. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public ValueTask QueryRecordAsync(ClaimsPrincipal principal, RecordRestriction? restriction, CancellationToken cancellationToken) => + BaseOperations.QueryRecordAsync(restriction + GetClaimRecordRestriction(principal), cancellationToken); + + /// + /// Queries database and returns a single modeled table record for the specified , + /// execution of query will apply . + /// + /// Claims principal which is making the request. + /// Field name expression used for sort order, include ASC or DESC as needed - does not include ORDER BY; defaults to primary keys. + /// Record restriction to apply. + /// A single modeled table record for the queried record. + /// + /// + /// If no record is found for specified , null will be returned. + /// + /// + /// This is a convenience call to + /// specifying the parameter with a limit of 1 record. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public T? QueryRecord(ClaimsPrincipal principal, string? orderByExpression, RecordRestriction? restriction) => + BaseOperations.QueryRecord(orderByExpression, restriction + GetClaimRecordRestriction(principal)); + + /// + /// Queries database and returns a single modeled table record for the specified , + /// execution of query will apply . + /// + /// Claims principal which is making the request. + /// Field name expression used for sort order, include ASC or DESC as needed - does not include ORDER BY; defaults to primary keys. + /// Record restriction to apply. + /// Propagates notification that operations should be canceled. + /// A single modeled table record for the queried record. + /// + /// + /// If no record is found for specified , null will be returned. + /// + /// + /// This is a convenience call to + /// specifying the parameter with a limit of 1 record. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public ValueTask QueryRecordAsync(ClaimsPrincipal principal, string? orderByExpression, RecordRestriction? restriction, CancellationToken cancellationToken) => + BaseOperations.QueryRecordAsync(orderByExpression, restriction + GetClaimRecordRestriction(principal), cancellationToken); + + /// + /// Queries database and returns a single modeled table record for the specified SQL filter + /// expression and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Restriction parameter values. + /// A single modeled table record for the queried record. + /// + /// + /// If no record is found for specified filter expression and parameters, null will be returned. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to + /// specifying the parameter with a limit of 1 record. + /// + /// + public T? QueryRecordWhere(ClaimsPrincipal principal, string? filterExpression, params object?[] parameters) => + QueryRecord(principal, new RecordRestriction(filterExpression, parameters)); + + /// + /// Queries database and returns a single modeled table record for the specified SQL filter + /// expression and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Propagates notification that operations should be canceled. + /// Restriction parameter values. + /// A single modeled table record for the queried record. + /// + /// + /// If no record is found for specified filter expression and parameters, null will be returned. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to + /// specifying the parameter with a limit of 1 record. + /// + /// + public ValueTask QueryRecordWhereAsync(ClaimsPrincipal principal, string? filterExpression, CancellationToken cancellationToken, params object?[] parameters) => + QueryRecordAsync(principal, new RecordRestriction(filterExpression, parameters), cancellationToken); + + /// + /// Queries database and returns modeled table records for the specified parameters. + /// + /// Claims principal which is making the request. + /// Field name expression used for sort order, include ASC or DESC as needed - does not include ORDER BY; defaults to primary keys. + /// Record restriction to apply, if any. + /// Limit of number of record to return. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// If no record or is provided, all rows will be returned. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public IEnumerable QueryRecords(ClaimsPrincipal principal, string? orderByExpression = null, RecordRestriction? restriction = null, int limit = -1) => + BaseOperations.QueryRecords(orderByExpression, restriction + GetClaimRecordRestriction(principal), limit); + + /// + /// Queries database and returns modeled table records for the specified parameters. + /// + /// Claims principal which is making the request. + /// Field name expression used for sort order, include ASC or DESC as needed - does not include ORDER BY; defaults to primary keys. + /// Record restriction to apply, if any. + /// Limit of number of record to return. + /// Propagates notification that operations should be canceled. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// If no record or is provided, all rows will be returned. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public IAsyncEnumerable QueryRecordsAsync(ClaimsPrincipal principal, string? orderByExpression = null, RecordRestriction? restriction = null, int limit = -1, CancellationToken cancellationToken = default) => + BaseOperations.QueryRecordsAsync(orderByExpression, restriction + GetClaimRecordRestriction(principal), limit, cancellationToken); + + /// + /// Queries database and returns modeled table records for the specified . + /// + /// Claims principal which is making the request. + /// Record restriction to apply. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This is a convenience call to only + /// specifying the parameter. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public IEnumerable QueryRecords(ClaimsPrincipal principal, RecordRestriction? restriction) => + BaseOperations.QueryRecords(restriction + GetClaimRecordRestriction(principal)); + + /// + /// Queries database and returns modeled table records for the specified . + /// + /// Claims principal which is making the request. + /// Record restriction to apply. + /// Propagates notification that operations should be canceled. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This is a convenience call to only + /// specifying the parameter. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public IAsyncEnumerable QueryRecordsAsync(ClaimsPrincipal principal, RecordRestriction? restriction, CancellationToken cancellationToken) => + BaseOperations.QueryRecordsAsync(restriction + GetClaimRecordRestriction(principal), cancellationToken); + + /// + /// Queries database and returns modeled table records for the specified SQL filter expression + /// and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Restriction parameter values. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to only + /// specifying the parameter. + /// + /// + public IEnumerable QueryRecordsWhere(ClaimsPrincipal principal, string? filterExpression, params object?[] parameters) => + BaseOperations.QueryRecords(new RecordRestriction(filterExpression, parameters) + GetClaimRecordRestriction(principal)); + + /// + /// Queries database and returns modeled table records for the specified SQL filter expression + /// and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Propagates notification that operations should be canceled. + /// Restriction parameter values. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to only + /// specifying the parameter. + /// + /// + public IAsyncEnumerable QueryRecordsWhereAsync(ClaimsPrincipal principal, string? filterExpression, CancellationToken cancellationToken, params object?[] parameters) => + BaseOperations.QueryRecordsAsync(new RecordRestriction(filterExpression, parameters) + GetClaimRecordRestriction(principal), cancellationToken); + + /// + /// Queries database and returns modeled table records for the specified sorting and paging parameters. + /// + /// Claims principal which is making the request. + /// Field name to order-by. + /// Sort ascending flag; set to false for descending. + /// Page number of records to return (1-based). + /// Current page size. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, + /// to maintain desired per-page sort order. Call to manually clear cache + /// when table contents are known to have changed. + /// + /// + /// If the specified has been marked with , + /// establishing the primary key cache operation will take longer to execute since query data will need to + /// be downloaded locally and decrypted so the proper sort order can be determined. + /// + /// + public IEnumerable QueryRecords(ClaimsPrincipal principal, string? sortField, bool ascending, int page, int pageSize) => + BaseOperations.QueryRecords(sortField, ascending, page, pageSize, GetClaimRecordRestriction(principal)); + + /// + /// Queries database and returns modeled table records for the specified sorting and paging parameters. + /// + /// Claims principal which is making the request. + /// Field name to order-by. + /// Sort ascending flag; set to false for descending. + /// Page number of records to return (1-based). + /// Current page size. + /// Propagates notification that operations should be canceled. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, + /// to maintain desired per-page sort order. Call to manually clear cache + /// when table contents are known to have changed. + /// + /// + /// If the specified has been marked with , + /// establishing the primary key cache operation will take longer to execute since query data will need to + /// be downloaded locally and decrypted so the proper sort order can be determined. + /// + /// + public IAsyncEnumerable QueryRecordsAsync(ClaimsPrincipal principal, string? sortField, bool ascending, int page, int pageSize, CancellationToken cancellationToken) => + BaseOperations.QueryRecordsAsync(sortField, ascending, page, pageSize, cancellationToken, GetClaimRecordRestriction(principal)); + + /// + /// Queries database and returns modeled table records for the specified sorting, paging and search parameters. + /// Search executed against fields modeled with . + /// + /// Claims principal which is making the request. + /// Field name to order-by. + /// Sort ascending flag; set to false for descending. + /// Page number of records to return (1-based). + /// Current page size. + /// Record Filters to be applied. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, + /// to maintain desired per-page sort order. Call to manually clear cache + /// when table contents are known to have changed. + /// + /// + /// If the specified has been marked with , + /// establishing the primary key cache operation will take longer to execute since query data will need to + /// be downloaded locally and decrypted so the proper sort order can be determined. + /// + /// + /// This is a convenience call to where restriction + /// is generated by using . + /// + /// + public IEnumerable QueryRecords(ClaimsPrincipal principal, string? sortField, bool ascending, int page, int pageSize, params IRecordFilter?[]? recordFilters) => + BaseOperations.QueryRecords(sortField, ascending, page, pageSize, (BaseOperations.GetSearchRestrictions(recordFilters) ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Queries database and returns modeled table records for the specified sorting, paging and search parameters. + /// Search executed against fields modeled with . + /// + /// Claims principal which is making the request. + /// Field name to order-by. + /// Sort ascending flag; set to false for descending. + /// Page number of records to return (1-based). + /// Current page size. + /// Propagates notification that operations should be canceled. + /// Record Filters to be applied. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, + /// to maintain desired per-page sort order. Call to manually clear cache + /// when table contents are known to have changed. + /// + /// + /// If the specified has been marked with , + /// establishing the primary key cache operation will take longer to execute since query data will need to + /// be downloaded locally and decrypted so the proper sort order can be determined. + /// + /// + /// This is a convenience call to where restriction + /// is generated by using . + /// + /// + public IAsyncEnumerable QueryRecordsAsync(ClaimsPrincipal principal, string? sortField, bool ascending, int page, int pageSize, CancellationToken cancellationToken, params IRecordFilter?[]? recordFilters) => + BaseOperations.QueryRecordsAsync(sortField, ascending, page, pageSize, cancellationToken, (BaseOperations.GetSearchRestrictions(recordFilters) ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Queries database and returns modeled table records for the specified sorting and paging parameters. + /// + /// Claims principal which is making the request. + /// Field name to order-by. + /// Sort ascending flag; set to false for descending. + /// Page number of records to return (1-based). + /// Current page size. + /// Record restrictions to apply, if any. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, + /// to maintain desired per-page sort order. Call to manually clear cache + /// when table contents are known to have changed. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If the specified has been marked with , + /// establishing the primary key cache operation will take longer to execute since query data will need to + /// be downloaded locally and decrypted so the proper sort order can be determined. + /// + /// + public IEnumerable QueryRecords(ClaimsPrincipal principal, string? sortField, bool ascending, int page, int pageSize, params RecordRestriction?[]? restrictions) => + BaseOperations.QueryRecords(sortField, ascending, page, pageSize, (restrictions ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Queries database and returns modeled table records for the specified sorting and paging parameters. + /// + /// Claims principal which is making the request. + /// Field name to order-by. + /// Sort ascending flag; set to false for descending. + /// Page number of records to return (1-based). + /// Current page size. + /// Propagates notification that operations should be canceled. + /// Record restrictions to apply, if any. + /// An enumerable of modeled table row instances for queried records. + /// + /// + /// This function is used for record paging. Primary keys are cached server-side, typically per user session, + /// to maintain desired per-page sort order. Call to manually clear cache + /// when table contents are known to have changed. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If the specified has been marked with , + /// establishing the primary key cache operation will take longer to execute since query data will need to + /// be downloaded locally and decrypted so the proper sort order can be determined. + /// + /// + public IAsyncEnumerable QueryRecordsAsync(ClaimsPrincipal principal, string? sortField, bool ascending, int page, int pageSize, [EnumeratorCancellation] CancellationToken cancellationToken, params RecordRestriction?[]? restrictions) => + BaseOperations.QueryRecordsAsync(sortField, ascending, page, pageSize, cancellationToken, (restrictions ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Gets total record count for the modeled table. + /// + /// Claims principal which is making the request. + /// + /// Total record count for the modeled table. + /// + public int QueryRecordCount(ClaimsPrincipal principal) => + BaseOperations.QueryRecordCount([GetClaimRecordRestriction(principal)]); + + /// + /// Gets total record count for the modeled table. + /// + /// Claims principal which is making the request. + /// Propagates notification that operations should be canceled. + /// + /// Total record count for the modeled table. + /// + public Task QueryRecordCountAsync(ClaimsPrincipal principal, CancellationToken cancellationToken) => + BaseOperations.QueryRecordCountAsync(cancellationToken, [GetClaimRecordRestriction(principal)]); + + /// + /// Gets the record count for the modeled table based on search parameter. + /// Search executed against fields modeled with . + /// + /// Claims principal which is making the request. + /// to be filtered by + /// Record count for the modeled table based on search parameter. + /// + /// This is a convenience call to where restriction + /// is generated by + /// + public int QueryRecordCount(ClaimsPrincipal principal, params IRecordFilter?[]? recordFilter) => + BaseOperations.QueryRecordCount((BaseOperations.GetSearchRestrictions(recordFilter) ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Gets the record count for the modeled table based on search parameter. + /// Search executed against fields modeled with . + /// + /// Claims principal which is making the request. + /// Propagates notification that operations should be canceled. + /// to be filtered by + /// Record count for the modeled table based on search parameter. + /// + /// This is a convenience call to where restriction + /// is generated by + /// + public Task QueryRecordCountAsync(ClaimsPrincipal principal, CancellationToken cancellationToken, params IRecordFilter?[]? recordFilter) => + BaseOperations.QueryRecordCountAsync(cancellationToken, (BaseOperations.GetSearchRestrictions(recordFilter) ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Gets the record count for the specified - or - total record + /// count for the modeled table if is null. + /// + /// Claims principal which is making the request. + /// Record restrictions to apply, if any. + /// + /// Record count for the specified - or - total record count + /// for the modeled table if no is provided. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + public int QueryRecordCount(ClaimsPrincipal principal, params RecordRestriction?[]? restrictions) => + BaseOperations.QueryRecordCount((restrictions ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Gets the record count for the specified - or - total record + /// count for the modeled table if is null. + /// + /// Claims principal which is making the request. + /// Propagates notification that operations should be canceled. + /// Record restrictions to apply, if any. + /// + /// Record count for the specified - or - total record count + /// for the modeled table if no is provided. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + public Task QueryRecordCountAsync(ClaimsPrincipal principal, CancellationToken cancellationToken, params RecordRestriction?[]? restrictions) => + BaseOperations.QueryRecordCountAsync(cancellationToken, (restrictions ?? []).Append(GetClaimRecordRestriction(principal)).ToArray()); + + /// + /// Gets the record count for the modeled table for the specified SQL filter expression and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Restriction parameter values. + /// Record count for the modeled table for the specified parameters. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public int QueryRecordCountWhere(ClaimsPrincipal principal, string? filterExpression, params object?[] parameters) => + BaseOperations.QueryRecordCount(new RecordRestriction(filterExpression, parameters) + GetClaimRecordRestriction(principal)); + + /// + /// Gets the record count for the modeled table for the specified SQL filter expression and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Propagates notification that operations should be canceled. + /// Restriction parameter values. + /// Record count for the modeled table for the specified parameters. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public Task QueryRecordCountWhereAsync(ClaimsPrincipal principal, string? filterExpression, CancellationToken cancellationToken, params object?[] parameters) => + BaseOperations.QueryRecordCountAsync(cancellationToken, new RecordRestriction(filterExpression, parameters) + GetClaimRecordRestriction(principal)); + + /// + /// Deletes the records referenced by the specified . + /// + /// Claims principal which is making the request. + /// Record restriction to apply + /// + /// Flag that determines if any existing should be applied. Defaults to + /// setting. + /// + /// Number of rows affected. + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// cannot be null. + public int DeleteRecord(ClaimsPrincipal principal, RecordRestriction? restriction, bool? applyRootQueryRestriction = null) => + BaseOperations.DeleteRecord(restriction + GetClaimRecordRestriction(principal), applyRootQueryRestriction); + + /// + /// Deletes the records referenced by the specified . + /// + /// Claims principal which is making the request. + /// Record restriction to apply + /// Propagates notification that operations should be canceled. + /// + /// Flag that determines if any existing should be applied. Defaults to + /// setting. + /// + /// Number of rows affected. + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// cannot be null. + public Task DeleteRecordAsync(ClaimsPrincipal principal, RecordRestriction? restriction, CancellationToken cancellationToken, bool? applyRootQueryRestriction = null) => + BaseOperations.DeleteRecordAsync(restriction + GetClaimRecordRestriction(principal), cancellationToken, applyRootQueryRestriction); + + /// + /// Deletes the records referenced by the specified SQL filter expression and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Restriction parameter values. + /// Number of rows affected. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public int DeleteRecordWhere(ClaimsPrincipal principal, string filterExpression, params object?[] parameters) => + DeleteRecord(principal, new RecordRestriction(filterExpression, parameters)); + + /// + /// Deletes the records referenced by the specified SQL filter expression and parameters. + /// + /// Claims principal which is making the request. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Propagates notification that operations should be canceled. + /// Restriction parameter values. + /// Number of rows affected. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public Task DeleteRecordWhereAsync(ClaimsPrincipal principal, string filterExpression, CancellationToken cancellationToken, params object?[] parameters) => + DeleteRecordAsync(principal, new RecordRestriction(filterExpression, parameters), cancellationToken); + + /// + /// Updates the database with the specified modeled table , + /// any model properties marked with will + /// be evaluated and applied before the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// Record to update. + /// Record restriction to apply, if any. + /// + /// Flag that determines if any existing should be applied. Defaults to + /// setting. + /// + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public int UpdateRecord(ClaimsPrincipal principal, T record, RecordRestriction? restriction = null, bool? applyRootQueryRestriction = null) => + BaseOperations.UpdateRecord(record, restriction + GetClaimRecordRestriction(principal), applyRootQueryRestriction); + + /// + /// Updates the database with the specified modeled table , + /// any model properties marked with will + /// be evaluated and applied before the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// Record to update. + /// Propagates notification that operations should be canceled. + /// Record restriction to apply, if any. + /// + /// Flag that determines if any existing should be applied. Defaults to + /// setting. + /// + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public Task UpdateRecordAsync(ClaimsPrincipal principal, T record, CancellationToken cancellationToken, RecordRestriction? restriction = null, bool? applyRootQueryRestriction = null) => + BaseOperations.UpdateRecordAsync(record, cancellationToken, restriction + GetClaimRecordRestriction(principal), applyRootQueryRestriction); + + /// + /// Updates the database with the specified modeled table + /// referenced by the specified SQL filter expression and parameters, any model properties + /// marked with will be evaluated and applied + /// before the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// Record to update. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Restriction parameter values. + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public int UpdateRecordWhere(ClaimsPrincipal principal, T record, string filterExpression, params object?[] parameters) => + UpdateRecord(principal, record, new RecordRestriction(filterExpression, parameters)); + + /// + /// Updates the database with the specified modeled table + /// referenced by the specified SQL filter expression and parameters, any model properties + /// marked with will be evaluated and applied + /// before the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// Record to update. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Propagates notification that operations should be canceled. + /// Restriction parameter values. + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public Task UpdateRecordWhereAsync(ClaimsPrincipal principal, T record, string filterExpression, CancellationToken cancellationToken, params object?[] parameters) => + UpdateRecordAsync(principal, record, cancellationToken, new RecordRestriction(filterExpression, parameters)); + + /// + /// Updates the database with the specified , any model properties + /// marked with will be evaluated and applied + /// before the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// of queried data to be updated. + /// Record restriction to apply, if any. + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public int UpdateRecord(ClaimsPrincipal principal, DataRow row, RecordRestriction? restriction = null) => + BaseOperations.UpdateRecord(row, restriction + GetClaimRecordRestriction(principal)); + + /// + /// Updates the database with the specified , any model properties + /// marked with will be evaluated and applied + /// before the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// of queried data to be updated. + /// Propagates notification that operations should be canceled. + /// Record restriction to apply, if any. + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// If any of the parameters reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + public Task UpdateRecordAsync(ClaimsPrincipal principal, DataRow row, CancellationToken cancellationToken, RecordRestriction? restriction = null) => + BaseOperations.UpdateRecordAsync(row, cancellationToken, restriction + GetClaimRecordRestriction(principal)); + + /// + /// Updates the database with the specified referenced by the + /// specified SQL filter expression and parameters, any model properties marked with + /// will be evaluated and applied before + /// the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// of queried data to be updated. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Restriction parameter values. + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public int UpdateRecordWhere(ClaimsPrincipal principal, DataRow row, string filterExpression, params object?[] parameters) => + UpdateRecord(principal, row, new RecordRestriction(filterExpression, parameters)); + + /// + /// Updates the database with the specified referenced by the + /// specified SQL filter expression and parameters, any model properties marked with + /// will be evaluated and applied before + /// the record is provided to the data source. + /// + /// Claims principal which is making the request. + /// of queried data to be updated. + /// + /// Filter SQL expression for restriction as a composite format string - does not include WHERE. + /// When escaping is needed for field names, use standard ANSI quotes. + /// + /// Propagates notification that operations should be canceled. + /// Restriction parameter values. + /// Number of rows affected. + /// + /// + /// Record restriction is only used for custom update expressions or in cases where modeled + /// table has no defined primary keys. + /// + /// + /// Each indexed parameter, e.g., "{0}", in the composite format + /// will be converted into query parameters where each of the corresponding values in the + /// collection will be applied as + /// values to an executed query. + /// + /// + /// If any of the specified reference a table field that is modeled with + /// either an or , then the function + /// will need to be called, replacing the target parameter with the + /// returned value so that the field value will be properly set prior to executing the database function. + /// + /// + /// If needed, field names that are escaped with standard ANSI quotes in the filter expression + /// will be updated to reflect what is defined in the user model. + /// + /// + /// This is a convenience call to . + /// + /// + public Task UpdateRecordWhereAsync(ClaimsPrincipal principal, DataRow row, string filterExpression, CancellationToken cancellationToken, params object?[] parameters) => + UpdateRecordAsync(principal, row, cancellationToken, new RecordRestriction(filterExpression, parameters)); + + #endregion + + #region [ Static ] + + private static readonly ClaimQueryRestrictionAttribute[]? s_claimQueryRestrictionAttributes; + + static SecureTableOperations() + { + if (typeof(T).TryGetAttributes(out ClaimQueryRestrictionAttribute[]? claimAttributes)) + s_claimQueryRestrictionAttributes = claimAttributes; + } + + #endregion +}