Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ public override IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
if (annotation.Name == NpgsqlAnnotationNames.UnloggedTable)
return new MethodCallCodeFragment(nameof(NpgsqlEntityTypeBuilderExtensions.IsUnlogged), annotation.Value);

if (annotation.Name == NpgsqlAnnotationNames.TablePartitioning && annotation.Value is TablePartitioning tablePartitioning)
return new MethodCallCodeFragment(nameof(NpgsqlEntityTypeBuilderExtensions.IsPartitioned), tablePartitioning.Type, tablePartitioning.PartitionKeyProperties.Select(x => x.Name));

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,62 @@ public static bool CanSetIsUnlogged(

#endregion

#region Partitioning
/// <summary>
/// Configures the entity to use table partitioning when targeting Npsql.
/// </summary>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <param name="tablePartitioningType">The type of partitioning to use on the table.</param>
/// <param name="partitionKeyPropertyNames">The entity's properties to use as key for the partitioning.</param>
/// <returns></returns>
public static EntityTypeBuilder IsPartitioned(
this EntityTypeBuilder entityTypeBuilder,
TablePartitioningType tablePartitioningType,
params string[] partitionKeyPropertyNames)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NotEmpty(partitionKeyPropertyNames, nameof(partitionKeyPropertyNames));

entityTypeBuilder.Metadata.SetTablePartitioning(tablePartitioningType, partitionKeyPropertyNames);

return entityTypeBuilder;
}

/// <summary>
/// Configures the entity to use table partitioning when targeting Npsql.
/// </summary>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <param name="tablePartitioningType">The type of partitioning to use on the table.</param>
/// <param name="partitionKeyPropertyNames">The entity's properties to use as key for the partitioning.</param>
/// <returns></returns>
public static EntityTypeBuilder IsPartitioned(
this EntityTypeBuilder entityTypeBuilder,
TablePartitioningType tablePartitioningType,
IEnumerable<string> partitionKeyPropertyNames)
=> IsPartitioned(
entityTypeBuilder,
tablePartitioningType,
partitionKeyPropertyNames.ToArray());

/// <summary>
/// Configures the entity to use table partitioning when targeting Npsql.
/// </summary>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <param name="tablePartitioningType">The type of partitioning to use on the table.</param>
/// <param name="partitionKeyPropertyNames">An expression representinng the entity's properties to use as key of the partition.</param>
public static EntityTypeBuilder<TEntity> IsPartitioned<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
TablePartitioningType tablePartitioningType,
Expression<Func<TEntity, object?>> partitionKeyPropertyExpression)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)IsPartitioned(
entityTypeBuilder,
tablePartitioningType,
Check.NotNull(partitionKeyPropertyExpression, nameof(partitionKeyPropertyExpression))
.GetMemberAccessList().Select(x => x.GetSimpleMemberName()));

#endregion

#region CockroachDB Interleave-in-parent

public static EntityTypeBuilder UseCockroachDbInterleaveInParent(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Utilities;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
Expand Down Expand Up @@ -88,6 +89,24 @@ public static bool SetIsUnlogged(

#endregion Unlogged

#region Partitioning
public static void SetTablePartitioning(
this IMutableEntityType entityType,
TablePartitioningType tablePartitioningType,
params string[] partitionKeyPropertyNames)
{
Check.NotEmpty(partitionKeyPropertyNames, nameof(partitionKeyPropertyNames));

var properties = partitionKeyPropertyNames
.Select(x => entityType.FindProperty(x))
.ToArray();

entityType.SetOrRemoveAnnotation(
NpgsqlAnnotationNames.TablePartitioning,
new TablePartitioning(tablePartitioningType, properties!));
}
#endregion

#region CockroachDb interleave in parent

public static CockroachDbInterleaveInParent GetCockroachDbInterleaveInParent(this IReadOnlyEntityType entityType)
Expand Down
1 change: 1 addition & 0 deletions src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static class NpgsqlAnnotationNames
public const string Tablespace = Prefix + "Tablespace";
public const string StorageParameterPrefix = Prefix + "StorageParameter:";
public const string UnloggedTable = Prefix + "UnloggedTable";
public const string TablePartitioning = Prefix + "TablePartitioning";
public const string IdentityOptions = Prefix + "IdentitySequenceOptions";
public const string TsVectorConfig = Prefix + "TsVectorConfig";
public const string TsVectorProperties = Prefix + "TsVectorProperties";
Expand Down
2 changes: 2 additions & 0 deletions src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public override IEnumerable<IAnnotation> For(ITable table)
yield return new Annotation(NpgsqlAnnotationNames.UnloggedTable, entityType.GetIsUnlogged());
if (entityType[CockroachDbAnnotationNames.InterleaveInParent] != null)
yield return new Annotation(CockroachDbAnnotationNames.InterleaveInParent, entityType[CockroachDbAnnotationNames.InterleaveInParent]);
if (entityType[NpgsqlAnnotationNames.TablePartitioning] != null)
yield return new Annotation(NpgsqlAnnotationNames.TablePartitioning, entityType[NpgsqlAnnotationNames.TablePartitioning]);
foreach (var storageParamAnnotation in entityType.GetAnnotations()
.Where(a => a.Name.StartsWith(NpgsqlAnnotationNames.StorageParameterPrefix, StringComparison.Ordinal)))
{
Expand Down
34 changes: 34 additions & 0 deletions src/EFCore.PG/Metadata/TablePartitioning.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Represents the Metadata for the partitioning of a table.
/// </summary>
public class TablePartitioning
{
/// <summary>
/// The type of partitioning to use.
/// </summary>
public TablePartitioningType Type { get; }

/// <summary>
/// The entity's properties to use as key for the partitioning.
/// </summary>
public IReadOnlyProperty[] PartitionKeyProperties { get; }

/// <summary>
/// Creates a <see cref="TablePartitioning"/>.
/// </summary>
/// <param name="type">The type of partitioning to use.</param>
/// <param name="partitionKeyProperties">The entity properties to use as key for the partitioning.</param>
/// <exception cref="ArgumentException"><paramref name="partitionKeyProperties"/></exception>
public TablePartitioning(TablePartitioningType type, IReadOnlyProperty[] partitionKeyProperties)
{
Check.NotEmpty(partitionKeyProperties, nameof(partitionKeyProperties));

Type = type;
PartitionKeyProperties = partitionKeyProperties;
}
}
}
29 changes: 29 additions & 0 deletions src/EFCore.PG/Metadata/TablePartitioningType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Microsoft.EntityFrameworkCore.Metadata
{
/// <summary>
/// Represents the supported forms of postgres table partitioning as defined in the official documentation:
/// https://www.postgresql.org/docs/current/ddl-partitioning.html#DDL-PARTITIONING-OVERVIEW
/// </summary>
public enum TablePartitioningType
{
/// <summary>
/// <para>
/// The table is partitioned into “ranges” defined by a key column or set of columns,
/// with no overlap between the ranges of values assigned to different partitions.
/// </para>
/// </summary>
Range,

/// <summary>
/// <para>
/// The table is partitioned by explicitly listing which key value(s) appear in each partition.
/// </para>
/// </summary>
List,

/// <summary>
/// The table is partitioned by specifying a modulus and a remainder for each partition
/// </summary>
Hash
}
}
54 changes: 54 additions & 0 deletions src/EFCore.PG/Migrations/NpgsqlMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ protected override void Generate(

builder.Append(")");

// Table Partitioning (https://www.postgresql.org/docs/current/ddl-partitioning.html)
if (operation[NpgsqlAnnotationNames.TablePartitioning] is TablePartitioning tablePartitioning)
{
var columnNames = tablePartitioning.PartitionKeyProperties
.Select(property =>
property.GetColumnName(StoreObjectIdentifier.Table(operation.Name, operation.Schema)))
.ToArray();

builder.AppendLine()
.Append("PARTITION BY ")
.Append(GetPartitionTypeString(tablePartitioning.Type))
.Append(" (")
.Append(ColumnList(columnNames!))
.Append(") ");
}

// CockroachDB "interleave in parent" (https://www.cockroachlabs.com/docs/stable/interleave-in-parent.html)
if (operation[CockroachDbAnnotationNames.InterleaveInParent] is string)
{
Expand Down Expand Up @@ -231,6 +247,12 @@ protected override void Generate(AlterTableOperation operation, IModel? model, M
{
var madeChanges = false;

// Table Partitioning may not be added after table creation
if (HasTablePartioningChanges(operation))
{
throw new ArgumentException($"When generating migrations SQL for {nameof(AlterTableOperation)}, can't alter a table's partitioning after it was created.");
}

// Storage parameters
var oldStorageParameters = GetStorageParameters(operation.OldTable);
var newStorageParameters = GetStorageParameters(operation);
Expand Down Expand Up @@ -1676,6 +1698,38 @@ private static string GenerateStorageParameterValue(object value)

#endregion Storage parameter utilities

#region Partitioning utilities
private static string GetPartitionTypeString(TablePartitioningType type)
{
return type switch
{
TablePartitioningType.Range => "Range",
TablePartitioningType.List => "List",
TablePartitioningType.Hash => "Hash",
_ => throw new NotSupportedException("Given TablePartitioningType is not supported as part of the NpgsqlMigrationSqlGenerator.")
};
}

private static bool HasTablePartioningChanges(AlterTableOperation operation)
{
var oldPartitioningConfiguration = operation.OldTable[NpgsqlAnnotationNames.TablePartitioning] as TablePartitioning;
var newPartitioningConfiguration = operation[NpgsqlAnnotationNames.TablePartitioning] as TablePartitioning;

var oldPartitionPropertyNames = oldPartitioningConfiguration
?.PartitionKeyProperties
?.Select(x => x.Name) ?? new List<string>();

var newPartitionPropertyNames = newPartitioningConfiguration
?.PartitionKeyProperties
?.Select(x => x.Name) ?? new List<string>();

return oldPartitioningConfiguration?.Type != newPartitioningConfiguration?.Type ||
!oldPartitionPropertyNames.SequenceEqual(newPartitionPropertyNames);


}
#endregion

#region Helpers

private string DelimitIdentifier(string identifier) =>
Expand Down
Loading