<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Signum.Utilities;
using Signum.Entities.Operations;
using Signum.Entities;
using Signum.Utilities.DataStructures;
using System.Reflection;
using Signum.Engine.Maps;
using Signum.Entities.Authorization;
using System.Threading;
using Signum.Engine.Authorization;
using Signum.Utilities.Reflection;
using Signum.Utilities.ExpressionTrees;
using Signum.Engine.Basics;
using Signum.Engine.DynamicQuery;
using Signum.Engine.Extensions.Properties;
using Signum.Entities.Reflection;
using System.Linq.Expressions;
using Signum.Entities.Basics;
using System.Data.Common;

namespace Signum.Engine.Operations
{
    public static class OperationLogic
    {
        static Expression<Func<OperationDN, IQueryable<OperationLogDN>>> LogOperationsExpression =
            o => Database.Query<OperationLogDN>().Where(a => a.Operation == o);
        public static IQueryable<OperationLogDN> LogOperations(this OperationDN o)
        {
            return LogOperationsExpression.Evaluate(o);
        }

        static Polymorphic<Dictionary<Enum, IOperation>> operations = new Polymorphic<Dictionary<Enum, IOperation>>(PolymorphicMerger.InheritDictionaryInterfaces, typeof(IIdentifiable));

        public static HashSet<Enum> RegisteredOperations
        {
            get { return operations.OverridenValues.SelectMany(a => a.Keys).ToHashSet(); }
        }


        static readonly Polymorphic<Tuple<bool>> protectedSaveTypes = new Polymorphic<Tuple<bool>>(PolymorphicMerger.InheritanceAndInterfaces, typeof(IIdentifiable));

        static readonly Variable<ImmutableStack<Type>> allowedTypes = Statics.ThreadVariable<ImmutableStack<Type>>("saveOperationsAllowedTypes");

        public static bool AllowSaveGlobally { get; set; }

        public static bool IsSaveProtectedAllowed(Type type)
        {
            if (AllowSaveGlobally)
                return true;

            var stack = allowedTypes.Value;
            return (stack != null && stack.Contains(type));
        }

        public static bool IsSaveProtected(Type type)
        {
            if (!typeof(IIdentifiable).IsAssignableFrom(type))
                return false;

            var tuple = protectedSaveTypes.TryGetValue(type);

            return tuple != null && tuple.Item1;
        }
        
        public static void SetProtectedSave<T>(bool? isProtected) where T : IIdentifiable
        {
            SetProtectedSave(typeof(T), isProtected);
        }

        public static void SetProtectedSave(Type type, bool? isProtected)
        {
            protectedSaveTypes.SetDefinition(type, isProtected == null ? null : Tuple.Create(isProtected.Value));
        }

        public static IDisposable AllowSave<T>() where T : class, IIdentifiable
        {
            return AllowSave(typeof(T));
        }

        private static IDisposable AllowSave(Type type)
        {
            allowedTypes.Value = (allowedTypes.Value ?? ImmutableStack<Type>.Empty).Push(type);

            return new Disposable(() => allowedTypes.Value = allowedTypes.Value.Pop());
        }




        public static void AssertStarted(SchemaBuilder sb)
        {
            sb.AssertDefined(ReflectionTools.GetMethodInfo(() => Start(null, null)));
        }

        public static void Start(SchemaBuilder sb, DynamicQueryManager dqm)
        {
            if (sb.NotDefined(MethodInfo.GetCurrentMethod()))
            {
                sb.Include<OperationDN>();
                sb.Include<OperationLogDN>();

                MultiEnumLogic<OperationDN>.Start(sb, () => RegisteredOperations);

                dqm[typeof(OperationLogDN)] = (from lo in Database.Query<OperationLogDN>()
                                               select new
                                               {
                                                   Entity = lo,
                                                   lo.Id,
                                                   Target = lo.Target,
                                                   lo.Operation,
                                                   User = lo.User,
                                                   lo.Start,
                                                   lo.End,
                                                   lo.Exception
                                               }).ToDynamic();

                dqm[typeof(OperationDN)] = (from lo in Database.Query<OperationDN>()
                                            select new
                                            {
                                                Entity = lo,
                                                lo.Id,
                                                lo.Name,
                                                lo.Key,
                                            }).ToDynamic();

                dqm.RegisterExpression((OperationDN o) => o.LogOperations());

                sb.Schema.EntityEventsGlobal.Saving += EntityEventsGlobal_Saving;

                sb.Schema.Table<OperationDN>().PreDeleteSqlSync += new Func<IdentifiableEntity, SqlPreCommand>(Operation_PreDeleteSqlSync);
                sb.Schema.Table<TypeDN>().PreDeleteSqlSync += new Func<IdentifiableEntity, SqlPreCommand>(Type_PreDeleteSqlSync);
            }
        }

        static SqlPreCommand Operation_PreDeleteSqlSync(IdentifiableEntity arg)
        {
            var t = Schema.Current.Table<OperationLogDN>();
            var f = (FieldReference)t.Fields["operation"].Field;

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", false, arg.Id);

            return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".Formato(t.Name, f.Name, param.ParameterName), new List<DbParameter> { param });
        }

        static SqlPreCommand Type_PreDeleteSqlSync(IdentifiableEntity arg)
        {
            var t = Schema.Current.Table<OperationLogDN>();
            var f = ((FieldImplementedByAll)t.Fields["entity"].Field).ColumnTypes;

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", false, arg.Id);

            return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".Formato(t.Name, f.Name, param.ParameterName), new List<DbParameter> { param });
        }

        static void EntityEventsGlobal_Saving(IdentifiableEntity ident)
        {
            if (ident.Modified == true && 
                IsSaveProtected(ident.GetType()) && 
                !IsSaveProtectedAllowed(ident.GetType()))
                throw new InvalidOperationException("Saving '{0}' is controlled by the operations. Use OperationLogic.AllowSave<{0}>() or execute {1}".Formato(
                    ident.GetType().Name,
                    operations.GetValue(ident.GetType()).Values
                    .Where(IsExecuteNoLite)
                    .CommaOr(o => MultiEnumDN.UniqueKey(o.Key))));<commit_msg>fix OperationLogic Type_PreDeleteSqlSync<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Signum.Utilities;
using Signum.Entities.Operations;
using Signum.Entities;
using Signum.Utilities.DataStructures;
using System.Reflection;
using Signum.Engine.Maps;
using Signum.Entities.Authorization;
using System.Threading;
using Signum.Engine.Authorization;
using Signum.Utilities.Reflection;
using Signum.Utilities.ExpressionTrees;
using Signum.Engine.Basics;
using Signum.Engine.DynamicQuery;
using Signum.Engine.Extensions.Properties;
using Signum.Entities.Reflection;
using System.Linq.Expressions;
using Signum.Entities.Basics;
using System.Data.Common;

namespace Signum.Engine.Operations
{
    public static class OperationLogic
    {
        static Expression<Func<OperationDN, IQueryable<OperationLogDN>>> LogOperationsExpression =
            o => Database.Query<OperationLogDN>().Where(a => a.Operation == o);
        public static IQueryable<OperationLogDN> LogOperations(this OperationDN o)
        {
            return LogOperationsExpression.Evaluate(o);
        }

        static Polymorphic<Dictionary<Enum, IOperation>> operations = new Polymorphic<Dictionary<Enum, IOperation>>(PolymorphicMerger.InheritDictionaryInterfaces, typeof(IIdentifiable));

        public static HashSet<Enum> RegisteredOperations
        {
            get { return operations.OverridenValues.SelectMany(a => a.Keys).ToHashSet(); }
        }


        static readonly Polymorphic<Tuple<bool>> protectedSaveTypes = new Polymorphic<Tuple<bool>>(PolymorphicMerger.InheritanceAndInterfaces, typeof(IIdentifiable));

        static readonly Variable<ImmutableStack<Type>> allowedTypes = Statics.ThreadVariable<ImmutableStack<Type>>("saveOperationsAllowedTypes");

        public static bool AllowSaveGlobally { get; set; }

        public static bool IsSaveProtectedAllowed(Type type)
        {
            if (AllowSaveGlobally)
                return true;

            var stack = allowedTypes.Value;
            return (stack != null && stack.Contains(type));
        }

        public static bool IsSaveProtected(Type type)
        {
            if (!typeof(IIdentifiable).IsAssignableFrom(type))
                return false;

            var tuple = protectedSaveTypes.TryGetValue(type);

            return tuple != null && tuple.Item1;
        }
        
        public static void SetProtectedSave<T>(bool? isProtected) where T : IIdentifiable
        {
            SetProtectedSave(typeof(T), isProtected);
        }

        public static void SetProtectedSave(Type type, bool? isProtected)
        {
            protectedSaveTypes.SetDefinition(type, isProtected == null ? null : Tuple.Create(isProtected.Value));
        }

        public static IDisposable AllowSave<T>() where T : class, IIdentifiable
        {
            return AllowSave(typeof(T));
        }

        private static IDisposable AllowSave(Type type)
        {
            allowedTypes.Value = (allowedTypes.Value ?? ImmutableStack<Type>.Empty).Push(type);

            return new Disposable(() => allowedTypes.Value = allowedTypes.Value.Pop());
        }




        public static void AssertStarted(SchemaBuilder sb)
        {
            sb.AssertDefined(ReflectionTools.GetMethodInfo(() => Start(null, null)));
        }

        public static void Start(SchemaBuilder sb, DynamicQueryManager dqm)
        {
            if (sb.NotDefined(MethodInfo.GetCurrentMethod()))
            {
                sb.Include<OperationDN>();
                sb.Include<OperationLogDN>();

                MultiEnumLogic<OperationDN>.Start(sb, () => RegisteredOperations);

                dqm[typeof(OperationLogDN)] = (from lo in Database.Query<OperationLogDN>()
                                               select new
                                               {
                                                   Entity = lo,
                                                   lo.Id,
                                                   Target = lo.Target,
                                                   lo.Operation,
                                                   User = lo.User,
                                                   lo.Start,
                                                   lo.End,
                                                   lo.Exception
                                               }).ToDynamic();

                dqm[typeof(OperationDN)] = (from lo in Database.Query<OperationDN>()
                                            select new
                                            {
                                                Entity = lo,
                                                lo.Id,
                                                lo.Name,
                                                lo.Key,
                                            }).ToDynamic();

                dqm.RegisterExpression((OperationDN o) => o.LogOperations());

                sb.Schema.EntityEventsGlobal.Saving += EntityEventsGlobal_Saving;

                sb.Schema.Table<OperationDN>().PreDeleteSqlSync += new Func<IdentifiableEntity, SqlPreCommand>(Operation_PreDeleteSqlSync);
                sb.Schema.Table<TypeDN>().PreDeleteSqlSync += new Func<IdentifiableEntity, SqlPreCommand>(Type_PreDeleteSqlSync);
            }
        }

        static SqlPreCommand Operation_PreDeleteSqlSync(IdentifiableEntity arg)
        {
            var t = Schema.Current.Table<OperationLogDN>();
            var f = (FieldReference)t.Fields["operation"].Field;

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", false, arg.Id);

            return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".Formato(t.Name, f.Name, param.ParameterName), new List<DbParameter> { param });
        }

        static SqlPreCommand Type_PreDeleteSqlSync(IdentifiableEntity arg)
        {
            var t = Schema.Current.Table<OperationLogDN>();
            var f = ((FieldImplementedByAll)t.Fields["target"].Field).ColumnTypes;

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", false, arg.Id);

            return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".Formato(t.Name, f.Name, param.ParameterName), new List<DbParameter> { param });
        }

        static void EntityEventsGlobal_Saving(IdentifiableEntity ident)
        {
            if (ident.Modified == true && 
                IsSaveProtected(ident.GetType()) && 
                !IsSaveProtectedAllowed(ident.GetType()))
                throw new InvalidOperationException("Saving '{0}' is controlled by the operations. Use OperationLogic.AllowSave<{0}>() or execute {1}".Formato(
                    ident.GetType().Name,
                    operations.GetValue(ident.GetType()).Values
                    .Where(IsExecuteNoLite)
                    .CommaOr(o => MultiEnumDN.UniqueKey(o.Key))));
{'fraction_non_alphanumeric': 0.09275030415801903, 'fraction_numerical': 0.0014313318542904171, 'mean_word_length': 1.8524188609920391, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 72, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2647309', 'n_tokens_mistral': 3334, 'n_tokens_neox': 3249, 'n_words': 655}