Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for postgres-xl table distribution #1697

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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 @@ -328,6 +328,134 @@ public static EntityTypeBuilder<TEntity> UseCockroachDbInterleaveInParent<TEntit

#endregion CockroachDB Interleave-in-parent

#region Postgres-xl Distribute By

public static EntityTypeBuilder PostgresXlDistributeBy(
this EntityTypeBuilder entityTypeBuilder,
PostgresXlDistributeByStrategy distributeByStrategy)
{
switch (distributeByStrategy)
{
case PostgresXlDistributeByStrategy.Replication:
case PostgresXlDistributeByStrategy.RoundRobin:
case PostgresXlDistributeByStrategy.Randomly:
var distribute = entityTypeBuilder.Metadata.GetPostgresXlDistributeBy();
distribute.DistributionStrategy = distributeByStrategy;
break;
}

return entityTypeBuilder;
}

public static EntityTypeBuilder<TEntity> PostgresXlDistributeBy<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
PostgresXlDistributeByStrategy distributeByStrategy)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)PostgresXlDistributeBy((EntityTypeBuilder)entityTypeBuilder, distributeByStrategy);

public static EntityTypeBuilder PostgresXlDistributeBy(
this EntityTypeBuilder entityTypeBuilder,
string propertyName,
PostgresXlDistributeByColumnFunction distributeByColumnFunction = PostgresXlDistributeByColumnFunction.None)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NotEmpty(propertyName, nameof(propertyName));

var distribute = entityTypeBuilder.Metadata.GetPostgresXlDistributeBy();

distribute.DistributionStrategy = PostgresXlDistributeByStrategy.None;
distribute.DistributeByColumnFunction = distributeByColumnFunction;
distribute.DistributeByPropertyName = propertyName;

return entityTypeBuilder;
}

public static EntityTypeBuilder PostgresXlDistributeBy<TEntity>(
this EntityTypeBuilder entityTypeBuilder,
Expression<Func<TEntity, object>> propertyExpression,
PostgresXlDistributeByColumnFunction distributeByColumnFunction = PostgresXlDistributeByColumnFunction.None)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NotNull(propertyExpression, nameof(propertyExpression));

entityTypeBuilder.PostgresXlDistributeBy(propertyExpression.GetPropertyAccess().Name, distributeByColumnFunction);

return entityTypeBuilder;
}

public static EntityTypeBuilder<TEntity> PostgresXlDistributeBy<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string propertyName,
PostgresXlDistributeByColumnFunction distributeByColumnFunction = PostgresXlDistributeByColumnFunction.None)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)PostgresXlDistributeBy(
(EntityTypeBuilder)entityTypeBuilder, propertyName, distributeByColumnFunction);

public static EntityTypeBuilder PostgresXlDistributionStyle(
this EntityTypeBuilder entityTypeBuilder,
PostgresXlDistributionStyle distributionStyle)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));

var distribute = entityTypeBuilder.Metadata.GetPostgresXlDistributeBy();

switch (distributionStyle)
{
case EntityFrameworkCore.PostgresXlDistributionStyle.Even:
case EntityFrameworkCore.PostgresXlDistributionStyle.All:
distribute.DistributionStyle = distributionStyle;
return entityTypeBuilder;
case EntityFrameworkCore.PostgresXlDistributionStyle.Key:
throw new ArgumentException(
$"Distribution style {EntityFrameworkCore.PostgresXlDistributionStyle.Key} was provided with no key. To use DISTSTYLE KEY, use {nameof(PostgresXlDistributionStyleKey)} instead.");
default:
throw new ArgumentOutOfRangeException(nameof(distributionStyle), distributionStyle, $@"Invalid {nameof(EntityFrameworkCore)} provided.");
}
}

public static EntityTypeBuilder<TEntity> PostgresXlDistributionStyle<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
PostgresXlDistributionStyle distributionStyle
)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)PostgresXlDistributionStyle((EntityTypeBuilder)entityTypeBuilder, distributionStyle);


public static EntityTypeBuilder PostgresXlDistributionStyleKey(
this EntityTypeBuilder entityTypeBuilder,
string distributionKey)
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NotEmpty(distributionKey, nameof(distributionKey));

var distribute = entityTypeBuilder.Metadata.GetPostgresXlDistributeBy();

distribute.DistributionStyle = EntityFrameworkCore.PostgresXlDistributionStyle.Key;
distribute.DistributeByPropertyName = distributionKey;
return entityTypeBuilder;
}

public static EntityTypeBuilder<TEntity> PostgresXlDistributionStyleKey<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, object>> distributionKeyExpression)
where TEntity : class
{
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
Check.NotNull(distributionKeyExpression, nameof(distributionKeyExpression));

entityTypeBuilder.PostgresXlDistributionStyleKey(distributionKeyExpression.GetPropertyAccess().Name);

return entityTypeBuilder;
}

public static EntityTypeBuilder<TEntity> PostgresXlDistributionStyleKey<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string distributionKey)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)PostgresXlDistributionStyleKey((EntityTypeBuilder)entityTypeBuilder, distributionKey);

#endregion Postgres-xl Distribute By

#region Obsolete

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,12 @@ public static CockroachDbInterleaveInParent GetCockroachDbInterleaveInParent(thi
=> new(entityType);

#endregion CockroachDb interleave in parent

#region Postgres-xl Distribute By

public static PostgresXlDistributeBy GetPostgresXlDistributeBy(this IReadOnlyEntityType entityType)
=> new(entityType);

#endregion Postgres-xl Distribute By
}
}
157 changes: 157 additions & 0 deletions src/EFCore.PG/Metadata/Internal/PostgresXlDistributeBy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable enable
using System;
using System.Text;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal
{
public class PostgresXlDistributeBy
{
private const string AnnotationName = PostgresXlDistributeByAnnotationNames.DistributeBy;

private readonly IReadOnlyAnnotatable _annotatable;

public virtual Annotatable Annotatable
=> (Annotatable)_annotatable;

public PostgresXlDistributeBy(IReadOnlyAnnotatable annotatable)
=> _annotatable = annotatable;

public virtual PostgresXlDistributeByStrategy DistributionStrategy
{
get => GetData().DistributionStrategy;
set
{
var (_, distributeByColumnFunction, distributionStyle, columnName) = GetData();
SetData(value, distributeByColumnFunction, distributionStyle, columnName);
}
}

public virtual PostgresXlDistributeByColumnFunction DistributeByColumnFunction
{
get => GetData().DistributeByColumnFunction;
set
{
var (distributionStrategy, _, distributionStyle, columnName) = GetData();
SetData(distributionStrategy, value, distributionStyle, columnName);
}
}

public virtual PostgresXlDistributionStyle DistributionStyle
{
get => GetData().DistributionStyle;
set
{
var (distributionStrategy, distributeByColumnFunction, _, columnName) = GetData();
SetData(distributionStrategy, distributeByColumnFunction, value, columnName);
}
}

public virtual string? DistributeByPropertyName
{
get => GetData().ColumnName;
set
{
var (distributionStrategy, distributeByColumnFunction, distributionStyle, _) = GetData();
SetData(distributionStrategy, distributeByColumnFunction, distributionStyle, value);
}
}

public void Deconstruct(out PostgresXlDistributeByStrategy distributionStrategy,
out PostgresXlDistributeByColumnFunction distributeByColumnFunction,
out PostgresXlDistributionStyle distributionStyle,
out string? distributeByColumnName)
{
distributionStrategy = DistributionStrategy;
distributeByColumnFunction = DistributeByColumnFunction;
distributionStyle = DistributionStyle;
distributeByColumnName = DistributeByPropertyName;
}

private (PostgresXlDistributeByStrategy DistributionStrategy, PostgresXlDistributeByColumnFunction DistributeByColumnFunction, PostgresXlDistributionStyle DistributionStyle, string? ColumnName) GetData()
{
var str = Annotatable[AnnotationName] as string;
return str == null
? (0, 0, 0, null)
: Deserialize(str);
}

private void SetData(
PostgresXlDistributeByStrategy distributionStrategy,
PostgresXlDistributeByColumnFunction distributeByColumnFunction,
PostgresXlDistributionStyle postgresXlDistributionStyle,
string? distributeByColumnName)
{
Annotatable[AnnotationName] = Serialize(distributionStrategy, distributeByColumnFunction, postgresXlDistributionStyle, distributeByColumnName);
}

private string Serialize(
PostgresXlDistributeByStrategy distributionStrategy,
PostgresXlDistributeByColumnFunction distributeByColumnFunction,
PostgresXlDistributionStyle postgresXlDistributionStyle,
string? distributeByColumnName)
{
var stringBuilder = new StringBuilder();

EscapeAndQuote(stringBuilder, distributionStrategy);
stringBuilder.Append(",");
EscapeAndQuote(stringBuilder, distributeByColumnFunction);
stringBuilder.Append(",");
EscapeAndQuote(stringBuilder, postgresXlDistributionStyle);
stringBuilder.Append(",");
EscapeAndQuote(stringBuilder, distributeByColumnName);

return stringBuilder.ToString();
}

private (PostgresXlDistributeByStrategy DistributionStrategy,
PostgresXlDistributeByColumnFunction DistributeByColumnFunction,
PostgresXlDistributionStyle DistributionStyle,
string? ColumnName)
Deserialize(string str)
{
var position = 0;
var distributionStrategy = Enum.Parse<PostgresXlDistributeByStrategy>(ExtractValue(str, ref position)!);
var distributeByColumnFunction = Enum.Parse<PostgresXlDistributeByColumnFunction>(ExtractValue(str, ref position)!);
var distributionStyle = Enum.Parse<PostgresXlDistributionStyle>(ExtractValue(str, ref position)!);
var columnName = ExtractValue(str, ref position);

return (distributionStrategy, distributeByColumnFunction, distributionStyle, columnName);
}

private static void EscapeAndQuote(StringBuilder builder, object? value)
{
builder.Append("'");

if (value != null)
{
builder.Append(value.ToString()?.Replace("'", "''"));
}

builder.Append("'");
}

private static string? ExtractValue(string value, ref int position)
{
position = value.IndexOf('\'', position) + 1;

var end = value.IndexOf('\'', position);

while (end + 1 < value.Length
&& value[end + 1] == '\'')
{
end = value.IndexOf('\'', end + 2);
}

var extracted = value.Substring(position, end - position).Replace("''", "'");
position = end + 1;

return extracted.Length == 0 ? null : extracted;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal
{
internal static class PostgresXlDistributeByAnnotationNames
{
public const string Prefix = NpgsqlAnnotationNames.Prefix + "PostgresXL:";

public const string DistributeBy = Prefix + "DistributeBy";
}
}
13 changes: 13 additions & 0 deletions src/EFCore.PG/Metadata/PostgresXlDistributeByColumnFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
public enum PostgresXlDistributeByColumnFunction
{
None = 0,
Hash = 1,
Modulo = 2,
}
}
14 changes: 14 additions & 0 deletions src/EFCore.PG/Metadata/PostgresXlDistributeByStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
public enum PostgresXlDistributeByStrategy
{
None = 0,
Replication = 1,
RoundRobin = 2,
Randomly = 3,
}
}
14 changes: 14 additions & 0 deletions src/EFCore.PG/Metadata/PostgresXlDistributionStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore
{
public enum PostgresXlDistributionStyle
{
None = 0,
Even = 1,
Key = 2,
All = 3,
}
}
Loading