/// <summary>
        /// Initializes a new instance of the <see cref="DatabaseRelationalKey"/> class.
        /// </summary>
        /// <param name="childTableName">The child table name.</param>
        /// <param name="childKey">The child key.</param>
        /// <param name="parentTableName">The parent table name.</param>
        /// <param name="parentKey">The parent key.</param>
        /// <param name="deleteAction">The delete action.</param>
        /// <param name="updateAction">The update action.</param>
        /// <exception cref="ArgumentException">
        /// <paramref name="updateAction"/> or <paramref name="deleteAction"/> will throw this exception if given an invalid enum value.
        /// Alternatively if the child key is not a foreign key this will also be thrown.
        /// Furthermore, if the parent key is not a unique or primary key, this will also be thrown.
        /// </exception>
        /// <exception cref="ArgumentNullException"><paramref name="parentTableName"/> or <paramref name="childTableName"/> or <paramref name="parentKey"/> or <paramref name="childKey"/> is <c>null</c></exception>
        public DatabaseRelationalKey(Identifier childTableName, IDatabaseKey childKey, Identifier parentTableName, IDatabaseKey parentKey, ReferentialAction deleteAction, ReferentialAction updateAction)
        {
            if (!deleteAction.IsValid())
            {
                throw new ArgumentException($"The { nameof(ReferentialAction) } provided must be a valid enum.", nameof(deleteAction));
            }
            if (!updateAction.IsValid())
            {
                throw new ArgumentException($"The { nameof(ReferentialAction) } provided must be a valid enum.", nameof(updateAction));
            }

            ChildTable  = childTableName ?? throw new ArgumentNullException(nameof(childTableName));
            ChildKey    = childKey ?? throw new ArgumentNullException(nameof(childKey));
            ParentTable = parentTableName ?? throw new ArgumentNullException(nameof(parentTableName));
            ParentKey   = parentKey ?? throw new ArgumentNullException(nameof(parentKey));

            if (ChildKey.KeyType != DatabaseKeyType.Foreign)
            {
                throw new ArgumentException($"The child key must be a foreign key, instead given a key of type '{ childKey.KeyType }'.", nameof(childKey));
            }
            if (ParentKey.KeyType != DatabaseKeyType.Primary && ParentKey.KeyType != DatabaseKeyType.Unique)
            {
                throw new ArgumentException($"The parent key must be a primary or unique key, instead given a key of type '{ parentKey.KeyType }'.", nameof(parentKey));
            }

            DeleteAction = deleteAction;
            UpdateAction = updateAction;
        }
        public ReflectionForeignKey(Identifier name, IDatabaseKey targetKey, IReadOnlyCollection <IDatabaseColumn> columns)
            : base(name, DatabaseKeyType.Foreign, columns)
        {
            if (targetKey == null)
            {
                throw new ArgumentNullException(nameof(targetKey));
            }
            if (targetKey.KeyType != DatabaseKeyType.Primary && targetKey.KeyType != DatabaseKeyType.Unique)
            {
                throw new ArgumentException("The parent key given to a foreign key must be a primary or unique key. Instead given: " + targetKey.KeyType.ToString(), nameof(targetKey));
            }
            if (columns.Count != targetKey.Columns.Count)
            {
                throw new ArgumentException("The number of columns given to a foreign key must match the number of columns in the target key", nameof(columns));
            }

            var columnTypes       = columns.Select(c => c.Type).ToList();
            var targetColumnTypes = targetKey.Columns.Select(c => c.Type).ToList();

            // if we're dealing with computed columns, we can't get the types easily so avoid checking the types
            var anyComputed           = columns.Any(c => c.IsComputed) || targetKey.Columns.Any(c => c.IsComputed);
            var columnTypesCompatible = ColumnTypesCompatible(columnTypes, targetColumnTypes);

            if (!anyComputed && !columnTypesCompatible)
            {
                throw new ArgumentException("Incompatible column types between source and target key columns.", nameof(columns));
            }
        }
        private static bool IsChildKeyUnique(IRelationalDatabaseTable table, IDatabaseKey key)
        {
            if (table == null)
            {
                throw new ArgumentNullException(nameof(table));
            }
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            var keyColumnNames   = key.Columns.Select(c => c.Name.LocalName).ToList();
            var matchesPkColumns = table.PrimaryKey
                                   .Match(
                pk =>
            {
                var pkColumnNames = pk.Columns.Select(c => c.Name.LocalName).ToList();
                return(keyColumnNames.SequenceEqual(pkColumnNames));
            },
                () => false
                );

            if (matchesPkColumns)
            {
                return(true);
            }

            var matchesUkColumns = table.UniqueKeys.Any(uk =>
            {
                var ukColumnNames = uk.Columns.Select(c => c.Name.LocalName).ToList();
                return(keyColumnNames.SequenceEqual(ukColumnNames));
            });

            if (matchesUkColumns)
            {
                return(true);
            }

            var uniqueIndexes = table.Indexes.Where(i => i.IsUnique).ToList();

            if (uniqueIndexes.Count == 0)
            {
                return(false);
            }

            return(uniqueIndexes.Any(i =>
            {
                var indexColumnExpressions = i.Columns
                                             .Select(ic => ic.DependentColumns.Select(dc => dc.Name.LocalName).FirstOrDefault() ?? ic.Expression)
                                             .ToList();
                return keyColumnNames.SequenceEqual(indexColumnExpressions);
            }));
        }
Exemple #4
0
        public static void Ctor_GivenUniqueKeysWithNullValue_ThrowsArgumentNullException()
        {
            Identifier tableName  = "test_table";
            var        columns    = new[] { Mock.Of <IDatabaseColumn>() };
            var        primaryKey = Option <IDatabaseKey> .None;
            var        uniqueKeys = new IDatabaseKey[] { null };
            var        parentKeys = Array.Empty <IDatabaseRelationalKey>();
            var        childKeys  = Array.Empty <IDatabaseRelationalKey>();
            var        indexes    = Array.Empty <IDatabaseIndex>();
            var        checks     = Array.Empty <IDatabaseCheckConstraint>();
            var        triggers   = Array.Empty <IDatabaseTrigger>();

            Assert.That(() => new RelationalDatabaseTable(tableName, columns, primaryKey, uniqueKeys, parentKeys, childKeys, indexes, checks, triggers), Throws.ArgumentNullException);
        }
Exemple #5
0
        public Constraints.PrimaryKeyConstraint MapPrimaryKey(Identifier tableName, IDatabaseKey primaryKey)
        {
            if (tableName == null)
            {
                throw new ArgumentNullException(nameof(tableName));
            }
            if (primaryKey == null)
            {
                throw new ArgumentNullException(nameof(primaryKey));
            }

            var pkConstraintName = primaryKey.Name.Match(pkName => pkName.LocalName, () => string.Empty);
            var columnNames      = primaryKey.Columns.Select(c => c.Name.LocalName).ToList();

            return(new Constraints.PrimaryKeyConstraint(
                       tableName,
                       pkConstraintName,
                       columnNames
                       ));
        }
Exemple #6
0
        public Constraints.UniqueKey MapUniqueKey(Identifier tableName, IDatabaseKey uniqueKey)
        {
            if (tableName == null)
            {
                throw new ArgumentNullException(nameof(tableName));
            }
            if (uniqueKey == null)
            {
                throw new ArgumentNullException(nameof(uniqueKey));
            }

            var ukConstraintName = uniqueKey.Name.Match(ukName => ukName.LocalName, () => string.Empty);
            var columnNames      = uniqueKey.Columns.Select(c => c.Name.LocalName).ToList();

            return(new Constraints.UniqueKey(
                       tableName,
                       ukConstraintName,
                       columnNames
                       ));
        }
        private static void ValidateColumnSetsCompatible(Identifier childTableName, IDatabaseKey childKey, Identifier parentTableName, IDatabaseKey parentKey)
        {
            var anyComputed = childKey.Columns.Any(c => c.IsComputed) || parentKey.Columns.Any(c => c.IsComputed);

            if (anyComputed)
            {
                return;
            }

            var childKeyColumnTypes  = childKey.Columns.Select(c => c.Type);
            var parentKeyColumnTypes = parentKey.Columns.Select(c => c.Type);

            var compatible = childKeyColumnTypes
                             .Zip(parentKeyColumnTypes, (a, b) => new { Column = a, TargetColumn = b })
                             .All(cc => IsTypeEquivalent(cc.Column, cc.TargetColumn));

            if (!compatible)
            {
                throw new Exception($"The column sets in the foreign key { childKey.Name } from { childTableName } to { parentKey.Name } in { parentTableName } are not compatible. Please check the declarations to ensure the column types are safe to create.");
            }
        }
        public ReflectionRelationalKey(Identifier childTableName, IDatabaseKey childKey, Identifier parentTableName, IDatabaseKey parentKey, ReferentialAction deleteAction, ReferentialAction updateAction)
        {
            if (childTableName == null)
            {
                throw new ArgumentNullException(nameof(childTableName));
            }
            if (childKey == null)
            {
                throw new ArgumentNullException(nameof(childKey));
            }
            if (parentTableName == null)
            {
                throw new ArgumentNullException(nameof(parentTableName));
            }
            if (parentKey == null)
            {
                throw new ArgumentNullException(nameof(parentKey));
            }
            if (!deleteAction.IsValid())
            {
                throw new ArgumentException($"The { nameof(ReferentialAction) } provided must be a valid enum.", nameof(deleteAction));
            }
            if (!updateAction.IsValid())
            {
                throw new ArgumentException($"The { nameof(ReferentialAction) } provided must be a valid enum.", nameof(updateAction));
            }

            // perform validation
            var relationalKey = new DatabaseRelationalKey(childTableName, childKey, parentTableName, parentKey, deleteAction, updateAction);

            ChildTable   = relationalKey.ChildTable;
            ChildKey     = relationalKey.ChildKey;
            ParentTable  = relationalKey.ParentTable;
            ParentKey    = relationalKey.ParentKey;
            DeleteAction = relationalKey.DeleteAction;
            UpdateAction = relationalKey.UpdateAction;

            ValidateColumnSetsCompatible(ChildTable, ChildKey, ParentTable, ParentKey);
        }
        public static int GetKeyHash(this IDatabaseKey key, Identifier tableName)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (tableName == null)
            {
                throw new ArgumentNullException(nameof(tableName));
            }

            var builder = new HashCode();

            builder.Add(tableName.GetHashCode());
            builder.Add(key.KeyType.GetHashCode());

            foreach (var column in key.Columns)
            {
                builder.Add(column.Name?.LocalName?.GetHashCode() ?? 0);
            }

            return(builder.ToHashCode());
        }
Exemple #10
0
        private InvocationExpressionSyntax BuildTableUniqueKeyForBuilder(IRelationalDatabaseTable table, IDatabaseKey uniqueKey)
        {
            if (table == null)
            {
                throw new ArgumentNullException(nameof(table));
            }
            if (uniqueKey == null)
            {
                throw new ArgumentNullException(nameof(uniqueKey));
            }

            var schemaNamespace    = NameTranslator.SchemaToNamespace(table.Name);
            var className          = NameTranslator.TableToClassName(table.Name);
            var qualifiedClassName = !schemaNamespace.IsNullOrWhiteSpace()
                ? schemaNamespace + "." + className
                : className;

            var entity    = GetEntityBuilder(qualifiedClassName);
            var ukBuilder = InvocationExpression(
                MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    entity,
                    IdentifierName(nameof(EntityTypeBuilder.HasAlternateKey))))
                            .WithArgumentList(
                ArgumentList(
                    SingletonSeparatedList(
                        Argument(
                            GenerateColumnSet(className, uniqueKey.Columns, false)))));

            uniqueKey.Name.IfSome(ukName =>
            {
                ukBuilder = InvocationExpression(
                    MemberAccessExpression(
                        SyntaxKind.SimpleMemberAccessExpression,
                        ukBuilder,
                        IdentifierName(nameof(RelationalKeyBuilderExtensions.HasName))))
                            .WithArgumentList(
                    ArgumentList(
                        SingletonSeparatedList(
                            Argument(
                                LiteralExpression(
                                    SyntaxKind.StringLiteralExpression,
                                    Literal(ukName.LocalName))))));
            });

            return(ukBuilder);
        }
        /// <summary>
        /// Analyses a database table and its primary key. Reports messages when the primary key columns are not the first columns in the table.
        /// </summary>
        /// <param name="table">A database table.</param>
        /// <param name="primaryKey">The primary key for the database table.</param>
        /// <returns>A set of linting messages used for reporting. An empty set indicates no issues discovered.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="table"/> or <paramref name="primaryKey"/> is <c>null</c>.</exception>
        protected IEnumerable <IRuleMessage> AnalyseTable(IRelationalDatabaseTable table, IDatabaseKey primaryKey)
        {
            if (table == null)
            {
                throw new ArgumentNullException(nameof(table));
            }
            if (primaryKey == null)
            {
                throw new ArgumentNullException(nameof(primaryKey));
            }

            var tableColumns = table.Columns;

            if (tableColumns.Empty())
            {
                return(Array.Empty <IRuleMessage>());
            }

            var pkColumnName    = primaryKey.Columns.Single().Name;
            var firstColumnName = table.Columns[0].Name;

            if (pkColumnName == firstColumnName)
            {
                return(Array.Empty <IRuleMessage>());
            }

            var message = BuildMessage(table.Name);

            return(new[] { message });
        }