//****************************************************************************************************** // RecordRestriction.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: // ---------------------------------------------------------------------------------------------------- // 03/13/2016 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Data; using System.Linq; using System.Runtime.CompilerServices; using GSF.Collections; namespace GSF.Data.Model { /// /// Defines a parameterized record restriction that can be applied to queries. /// /// /// For versatility, values in the array are mutable, however, this makes the /// array vulnerable to unintended updates for long-lived instances. Consequently, the normal use-case /// of record restriction instances should be considered temporal. If an instance needs to be cached, /// consider use of the function to reduce risk of unintended array updates. /// public class RecordRestriction : IEquatable { #region [ Members ] // Fields /// /// 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 restriction parameter values. /// /// /// If any of the 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 any database function. /// public readonly object[] Parameters; #endregion #region [ Constructors ] /// /// 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. /// /// Restriction 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 be applied as /// values to an executed query. /// /// /// If any of the 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 any 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. /// /// public RecordRestriction(string filterExpression, params object[] parameters) { FilterExpression = filterExpression; Parameters = parameters; } #endregion #region [ Properties ] /// /// Gets or sets field value for the specified . /// /// Index into field array. /// field value for the specified . public object this[int index] { get { return Parameters[index]; } set { Parameters[index] = value; } } #endregion #region [ Methods ] /// /// Creates a deep copy of this record restriction. /// /// Deep copy of this record restriction. public RecordRestriction Clone() { return Clone(this); } /// /// Determines whether the specified object is equal to the current object. /// /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public override bool Equals(object obj) { RecordRestriction other = obj as RecordRestriction; return (object)other != null && Equals(other); } /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals(RecordRestriction other) { if (ReferenceEquals(this, other)) return true; if (FilterExpression.Equals(other?.FilterExpression, StringComparison.Ordinal)) return Parameters.CompareTo(other?.Parameters, true) == 0; return false; } /// /// Serves as the default hash function. /// /// A hash code for the current . public override int GetHashCode() { unchecked { return ((FilterExpression?.GetHashCode() ?? 0) * 397) ^ (Parameters?.GetHashCode() ?? 0); } } #endregion #region [ Operators ] /// /// Implicitly converts a based filter expression into a . /// /// Operand. /// Record operation representing the specified filter expression. public static implicit operator RecordRestriction(string value) => new RecordRestriction(value); /// /// Compares to record restrictions for equality. /// /// left operand. /// right operand. /// true if record restrictions are equal; otherwise, false. public static bool operator ==(RecordRestriction left, RecordRestriction right) { if ((object)left == null && (object)right == null) return true; return left?.Equals(right) ?? false; } /// /// Compares to record restrictions for inequality. /// /// left operand. /// right operand. /// true if record restrictions are not equal; otherwise, false. public static bool operator !=(RecordRestriction left, RecordRestriction right) { if ((object)left == null && (object)right == null) return false; return !left?.Equals(right) ?? true; } /// /// Combines two record restrictions with an AND condition. /// /// left operand. /// right operand. /// New combined record restriction. /// /// If both parameters are null, result will be null. If one parameter is null /// and the other parameter is not, the non-null parameter will be returned. Equally, if the /// of both parameters are null, empty or only whitespace, /// then the result will be null. If one parameter has a filter expression that is /// null, empty or whitespace and the other parameter's filter expression is defined, then /// the parameter that has a filter expression will be returned. /// public static RecordRestriction operator +(RecordRestriction left, RecordRestriction right) { return CombineAnd(left, right); } /// /// Combines two record restrictions with an AND condition. /// /// left operand. /// right operand. /// New combined record restriction. /// /// If both parameters are null, result will be null. If one parameter is null /// and the other parameter is not, the non-null parameter will be returned. Equally, if the /// of both parameters are null, empty or only whitespace, /// then the result will be null. If one parameter has a filter expression that is /// null, empty or whitespace and the other parameter's filter expression is defined, then /// the parameter that has a filter expression will be returned. /// public static RecordRestriction operator &(RecordRestriction left, RecordRestriction right) { return CombineAnd(left, right); } /// /// Combines two record restrictions with an OR condition. /// /// left operand. /// right operand. /// New combined record restriction. /// /// If both parameters are null, result will be null. If one parameter is null /// and the other parameter is not, the non-null parameter will be returned. Equally, if the /// of both parameters are null, empty or only whitespace, /// then the result will be null. If one parameter has a filter expression that is /// null, empty or whitespace and the other parameter's filter expression is defined, then /// the parameter that has a filter expression will be returned. /// public static RecordRestriction operator |(RecordRestriction left, RecordRestriction right) { return CombineOr(left, right); } #endregion #region [ Static ] // Static Methods /// /// Creates a deep copy of the record restriction. /// /// Record restriction to clone. /// Deep copy of the record restriction. public static RecordRestriction Clone(RecordRestriction source) { if ((object)source == null) return null; object[] parameters = source.Parameters; if ((object)parameters != null && parameters.Length > 0) { parameters = new object[source.Parameters.Length]; Array.Copy(source.Parameters, parameters, source.Parameters.Length); } return new RecordRestriction(source.FilterExpression, parameters); } /// /// Combines two record restrictions with an AND condition. /// /// Left operand. /// Right operand. /// New combined record restriction. /// /// If both parameters are null, result will be null. If one parameter is null /// and the other parameter is not, the non-null parameter will be returned. Equally, if the /// of both parameters are null, empty or only whitespace, /// then the result will be null. If one parameter has a filter expression that is /// null, empty or whitespace and the other parameter's filter expression is defined, then /// the parameter that has a filter expression will be returned. /// public static RecordRestriction CombineAnd(RecordRestriction left, RecordRestriction right) { return Combine(left, right, "AND"); } /// /// Combines two record restrictions with an OR condition. /// /// Left operand. /// Right operand. /// New combined record restriction. /// /// If both parameters are null, result will be null. If one parameter is null /// and the other parameter is not, the non-null parameter will be returned. Equally, if the /// of both parameters are null, empty or only whitespace, /// then the result will be null. If one parameter has a filter expression that is /// null, empty or whitespace and the other parameter's filter expression is defined, then /// the parameter that has a filter expression will be returned. /// public static RecordRestriction CombineOr(RecordRestriction left, RecordRestriction right) { return Combine(left, right, "OR"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static RecordRestriction Combine(RecordRestriction left, RecordRestriction right, string operation) { // Check for null operands if ((object)left == null && (object)right == null) return null; if ((object)left == null) return right; if ((object)right == null) return left; // Check for empty filter expressions bool leftEmpty = string.IsNullOrWhiteSpace(left.FilterExpression); bool rightEmpty = string.IsNullOrWhiteSpace(right.FilterExpression); if (leftEmpty && rightEmpty) return null; if (leftEmpty) return right; if (rightEmpty) return left; // Check for missing parameters int leftLength = left.Parameters?.Length ?? 0; int rightLength = right.Parameters?.Length ?? 0; if (leftLength == 0 && rightLength == 0) return new RecordRestriction($"({left.FilterExpression}) {operation} ({right.FilterExpression})"); object[] parameters = leftLength == 0 ? right.Parameters : (rightLength == 0 ? left.Parameters : left.Parameters.Combine(right.Parameters)); object[] offsetArgs = Enumerable.Range(leftLength, rightLength).Select(index => (object)$"{{{index}}}").ToArray(); return new RecordRestriction($"({left.FilterExpression}) {operation} ({string.Format(right.FilterExpression, offsetArgs)})", parameters); } #endregion } }