File: System\Data\Filter\DataExpression.cs
Web Access
Project: src\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Data
{
    internal sealed class DataExpression : IFilter
    {
        internal string? _originalExpression;  // original, unoptimized string
 
        private readonly bool _parsed;
        private bool _bound;
        private ExpressionNode? _expr;
        private DataTable? _table;
        private readonly StorageType _storageType;
        private readonly Type? _dataType;  // This set if the expression is part of ExpressionCoulmn
        private DataColumn[] _dependency = Array.Empty<DataColumn>();
 
        [RequiresUnreferencedCode("Members of types used in the expression might be trimmed")]
        internal DataExpression(DataTable? table, string? expression) : this(table, expression, null)
        {
        }
 
        [RequiresUnreferencedCode("Members of types used in the expression might be trimmed")]
        internal DataExpression(DataTable? table, string? expression, Type? type)
        {
            ExpressionParser parser = new ExpressionParser(table);
            parser.LoadExpression(expression);
 
            _originalExpression = expression;
            _expr = null;
 
            // Note: nobody seems to pass a null expression in the codebase
            if (expression != null)
            {
                _storageType = DataStorage.GetStorageType(type);
                if (_storageType == StorageType.BigInteger)
                {
                    throw ExprException.UnsupportedDataType(type!);
                }
 
                _dataType = type;
                _expr = parser.Parse();
                _parsed = true;
                if (_expr != null && table != null)
                {
                    Bind(table);
                }
                else
                {
                    _bound = false;
                }
            }
        }
 
        internal string Expression
        {
            get
            {
                return _originalExpression ?? ""; // CONSIDER: return optimized expression here (if bound)
            }
        }
 
        internal ExpressionNode? ExpressionNode
        {
            get
            {
                return _expr;
            }
        }
 
        internal bool HasValue
        {
            get
            {
                return (null != _expr);
            }
        }
 
        internal void Bind(DataTable? table)
        {
            _table = table;
 
            if (table == null)
                return;
 
            if (_expr != null)
            {
                Debug.Assert(_parsed, "Invalid calling order: Bind() before Parse()");
                List<DataColumn> list = new List<DataColumn>();
                _expr.Bind(table, list);
                _expr = _expr.Optimize();
                _table = table;
                _bound = true;
                _dependency = list.ToArray();
            }
        }
 
        internal bool DependsOn(DataColumn column)
        {
            if (_expr != null)
            {
                return _expr.DependsOn(column);
            }
            else
            {
                return false;
            }
        }
 
        internal object Evaluate()
        {
            return Evaluate((DataRow?)null, DataRowVersion.Default);
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "Constructors taking expression are marked as unsafe")]
        internal object Evaluate(DataRow? row, DataRowVersion version)
        {
            object? result;
 
            if (!_bound)
            {
                Bind(_table);
            }
            // Note: _expr is always non-null in the current codebase
            if (_expr != null)
            {
                result = _expr.Eval(row, version);
                // if the type is a SqlType (StorageType.Uri < _storageType), convert DBNull values.
                if (result != DBNull.Value || StorageType.Uri < _storageType)
                {
                    // we need to convert the return value to the column.Type;
                    try
                    {
                        if (StorageType.Object != _storageType)
                        {
                            // TODO: _dataType can be null, probably a bug
                            result = SqlConvert.ChangeType2(result, _storageType, _dataType!, _table!.FormatProvider);
                        }
                    }
                    catch (Exception e) when (ADP.IsCatchableExceptionType(e))
                    {
                        ExceptionBuilder.TraceExceptionForCapture(e);
                        throw ExprException.DatavalueConversion(result, _dataType!);
                    }
                }
            }
            else
            {
                result = null;
            }
            return result!;
        }
 
        internal object Evaluate(DataRow[] rows)
        {
            return Evaluate(rows, DataRowVersion.Default);
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "Constructors taking expression are marked as unsafe")]
        internal object Evaluate(DataRow[] rows, DataRowVersion version)
        {
            if (!_bound)
            {
                Bind(_table);
            }
            if (_expr != null)
            {
                List<int> recordList = new List<int>();
                foreach (DataRow row in rows)
                {
                    if (row.RowState == DataRowState.Deleted)
                        continue;
                    if (version == DataRowVersion.Original && row._oldRecord == -1)
                        continue;
                    recordList.Add(row.GetRecordFromVersion(version));
                }
                int[] records = recordList.ToArray();
                return _expr.Eval(records);
            }
            else
            {
                return DBNull.Value;
            }
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "Constructors taking expression are marked as unsafe")]
        public bool Invoke(DataRow row, DataRowVersion version)
        {
            if (_expr == null)
                return true;
 
            if (row == null)
            {
                throw ExprException.InvokeArgument();
            }
            object val = _expr.Eval(row, version);
            bool result;
            try
            {
                result = ToBoolean(val);
            }
            catch (EvaluateException)
            {
                throw ExprException.FilterConversion(Expression);
            }
            return result;
        }
 
        internal DataColumn[] GetDependency()
        {
            Debug.Assert(_dependency != null, "GetDependencies: null, we should have created an empty list");
            return _dependency;
        }
 
        internal bool IsTableAggregate()
        {
            if (_expr != null)
                return _expr.IsTableConstant();
            else
                return false;
        }
 
        internal static bool IsUnknown(object value)
        {
            return DataStorage.IsObjectNull(value);
        }
 
        internal bool HasLocalAggregate()
        {
            if (_expr != null)
                return _expr.HasLocalAggregate();
            else
                return false;
        }
 
        internal bool HasRemoteAggregate()
        {
            if (_expr != null)
                return _expr.HasRemoteAggregate();
            else
                return false;
        }
 
        internal static bool ToBoolean(object value)
        {
            if (IsUnknown(value))
                return false;
            if (value is bool)
                return (bool)value;
            if (value is SqlBoolean)
            {
                return (((SqlBoolean)value).IsTrue);
            }
            //check for SqlString is not added, value for true and false should be given with String, not with SqlString
            if (value is string)
            {
                try
                {
                    return bool.Parse((string)value);
                }
                catch (Exception e) when (ADP.IsCatchableExceptionType(e))
                {
                    ExceptionBuilder.TraceExceptionForCapture(e);
                    throw ExprException.DatavalueConversion(value, typeof(bool));
                }
            }
 
            throw ExprException.DatavalueConversion(value, typeof(bool));
        }
    }
}