Skip to content

Commit

Permalink
Merge pull request #1528 from Invinsiber/feature/postgresql-overridin…
Browse files Browse the repository at this point in the history
…g-system-value

Add support for PostgreSQL Overriding System|User Value
  • Loading branch information
jzabroski committed Oct 18, 2021
2 parents a2eac60 + 39e3d49 commit a0ebc3e
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 2 deletions.
@@ -0,0 +1,73 @@
#region License
// Copyright (c) 2021, FluentMigrator Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using System;

using FluentMigrator.Builders.Insert;
using FluentMigrator.Infrastructure;
using FluentMigrator.Infrastructure.Extensions;

namespace FluentMigrator.Postgres
{
public static partial class PostgresExtensions
{
public const string OverridingIdentityValues = "PostgresOverridingIdentityValues";

/// <summary>
/// Adds an OVERRIDING SYSTEM VALUE clause in the current <see cref="IInsertDataSyntax"/> expression.
/// This enables the system-generated values to be overriden with the user-specified explicit values (other than <c>DEFAULT</c>)
/// for identity columns defined as <c>GENERATED ALWAYS</c>
/// </summary>
/// <param name="expression">The current <see cref="IInsertDataSyntax"/> expression</param>
/// <returns>The current <see cref="IInsertDataSyntax"/> expression</returns>
public static IInsertDataSyntax WithOverridingSystemValue(this IInsertDataSyntax expression) =>
SetOverridingIdentityValues(expression, PostgresOverridingIdentityValuesType.System, nameof(WithOverridingSystemValue));

/// <summary>
/// Adds an OVERRIDING USER VALUE clause in the current <see cref="IInsertDataSyntax"/> expression.
/// Any user-specified values will be ignored and the system-generated values will be applied
/// for identity columns defined as <c>GENERATED BY DEFAULT</c>
/// </summary>
/// <param name="expression">The current <see cref="IInsertDataSyntax"/> expression</param>
/// <returns>The current <see cref="IInsertDataSyntax"/> expression</returns>
public static IInsertDataSyntax WithOverridingUserValue(this IInsertDataSyntax expression) =>
SetOverridingIdentityValues(expression, PostgresOverridingIdentityValuesType.User, nameof(WithOverridingUserValue));

/// <summary>
/// Set the additional feature for overriding identity values with the specified <see cref="PostgresOverridingIdentityValuesType"/>
/// on the provided <see cref="IInsertDataSyntax"/> expression
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
private static IInsertDataSyntax SetOverridingIdentityValues(
IInsertDataSyntax expression,
PostgresOverridingIdentityValuesType overridingType,
string callingMethod)
{
if (!(expression is ISupportAdditionalFeatures castExpression))
{
throw new InvalidOperationException(
string.Format(
ErrorMessages.MethodXMustBeCalledOnObjectImplementingY,
callingMethod,
nameof(ISupportAdditionalFeatures)));
}

castExpression.SetAdditionalFeature(OverridingIdentityValues, overridingType);

return expression;
}
}
}
@@ -0,0 +1,33 @@
#region License
// Copyright (c) 2021, FluentMigrator Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace FluentMigrator
{
/// <summary>
/// Determines if the system-generated values or the user-supplied values are applied for identity columns
/// </summary>
public enum PostgresOverridingIdentityValuesType
{
/// <summary>
/// Overrides the system-generated values with user-supplied values for identity columns
/// </summary>
System,
/// <summary>
/// Ignores the user-supplied values and applies the system-generated values for identity columns
/// </summary>
User,
}
}
Expand Up @@ -16,6 +16,8 @@

using System.Collections.Generic;

using FluentMigrator.Expressions;
using FluentMigrator.Infrastructure.Extensions;
using FluentMigrator.Postgres;

using JetBrains.Annotations;
Expand Down Expand Up @@ -61,5 +63,21 @@ protected override HashSet<string> GetAllowIndexStorageParameters()

return allow;
}

/// <inheritdoc />
protected override string GetOverridingIdentityValuesString(InsertDataExpression expression)
{
if (!expression.AdditionalFeatures.ContainsKey(PostgresExtensions.OverridingIdentityValues))
{
return string.Empty;
}

var overridingIdentityValues =
expression.GetAdditionalFeature<PostgresOverridingIdentityValuesType>(
PostgresExtensions.OverridingIdentityValues);

return string.Format(" OVERRIDING {0} VALUE",
overridingIdentityValues == PostgresOverridingIdentityValuesType.User ? "USER" : "SYSTEM");
}
}
}
Expand Up @@ -444,7 +444,11 @@ public override string Generate(InsertDataExpression expression)

var columns = GetColumnList(columnNames);
var data = GetDataList(columnData);
result.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2});", Quoter.QuoteTableName(expression.TableName, expression.SchemaName), columns, data);
result.AppendFormat("INSERT INTO {0} ({1}){3} VALUES ({2});",
Quoter.QuoteTableName(expression.TableName, expression.SchemaName),
columns,
data,
GetOverridingIdentityValuesString(expression));
}
return result.ToString();
}
Expand Down Expand Up @@ -629,5 +633,15 @@ public override string Generate(DeleteSequenceExpression expression)
{
return string.Format("{0};", base.Generate(expression));
}

protected virtual string GetOverridingIdentityValuesString(InsertDataExpression expression)
{
if (!expression.AdditionalFeatures.ContainsKey(PostgresExtensions.OverridingIdentityValues))
{
return string.Empty;
}

throw new NotSupportedException("The current version doesn't support OVERRIDING {SYSTEM|USER} VALUE. Please use Postgres 10+.");
}
}
}
Expand Up @@ -20,6 +20,7 @@

using FluentMigrator.Builders.Insert;
using FluentMigrator.Expressions;
using FluentMigrator.Postgres;
using FluentMigrator.SqlServer;

using NUnit.Framework;
Expand Down Expand Up @@ -96,5 +97,81 @@ public void SqlServerIdentityInsertCalledTwiceAddsCorrectAdditionalFeature()
expression.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(SqlServerExtensions.IdentityInsert, true));
}

[Test]
public void PostgresOverridingSystemValueAddsCorrectAdditionalFeature()
{
var expression = new InsertDataExpression();
var builder = new InsertDataExpressionBuilder(expression);
builder.WithOverridingSystemValue();

expression.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(
PostgresExtensions.OverridingIdentityValues,
PostgresOverridingIdentityValuesType.System));
}

[Test]
public void PostgresOverridingSystemValueCalledTwiceAddsCorrectAdditionalFeature()
{
var expression = new InsertDataExpression();
var builder = new InsertDataExpressionBuilder(expression);
builder.WithOverridingSystemValue().WithOverridingSystemValue();

expression.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(
PostgresExtensions.OverridingIdentityValues,
PostgresOverridingIdentityValuesType.System));
}

[Test]
public void PostgresOverridingUserValueAddsCorrectAdditionalFeature()
{
var expression = new InsertDataExpression();
var builder = new InsertDataExpressionBuilder(expression);
builder.WithOverridingUserValue();

expression.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(
PostgresExtensions.OverridingIdentityValues,
PostgresOverridingIdentityValuesType.User));
}

[Test]
public void PostgresOverridingUserValueCalledTwiceAddsCorrectAdditionalFeature()
{
var expression = new InsertDataExpression();
var builder = new InsertDataExpressionBuilder(expression);
builder.WithOverridingUserValue().WithOverridingUserValue();

expression.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(
PostgresExtensions.OverridingIdentityValues,
PostgresOverridingIdentityValuesType.User));
}

[Test]
public void PostgresOverridingIdentityValuesCalledWithDifferentTypeAddsCorrectAdditionalFeature()
{
// If both WithOverridingSystemValue() and WithOverridingUserValue() are called on the same expression,
// then the latest value should be set in the additional features

var expressionForUserValue = new InsertDataExpression();
var builderForUserValue = new InsertDataExpressionBuilder(expressionForUserValue);
builderForUserValue.WithOverridingSystemValue().WithOverridingUserValue();

var expressionForSystemValue = new InsertDataExpression();
var builderForSystemValue = new InsertDataExpressionBuilder(expressionForSystemValue);
builderForSystemValue.WithOverridingUserValue().WithOverridingSystemValue();

expressionForUserValue.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(
PostgresExtensions.OverridingIdentityValues,
PostgresOverridingIdentityValuesType.User));
expressionForSystemValue.AdditionalFeatures.ShouldContain(
new KeyValuePair<string, object>(
PostgresExtensions.OverridingIdentityValues,
PostgresOverridingIdentityValuesType.System));
}
}
}
@@ -1,3 +1,6 @@
using System;

using FluentMigrator.Postgres;
using FluentMigrator.Runner.Generators.Postgres;
using FluentMigrator.Runner.Processors.Postgres;

Expand All @@ -16,7 +19,7 @@ public class PostgresDataTests : BaseDataTests
public void Setup()
{
var quoter = new PostgresQuoter(new PostgresOptions());
Generator = new PostgresGenerator(quoter);
Generator = CreateGenerator(quoter);
}

[Test]
Expand Down Expand Up @@ -128,6 +131,24 @@ public override void CanInsertGuidDataWithDefaultSchema()
result.ShouldBe(string.Format("INSERT INTO \"public\".\"TestTable1\" (\"guid\") VALUES ('{0}');", GeneratorTestHelper.TestGuid));
}

[Test]
public virtual void CanInsertWithOverridingSystemValue()
{
var expression = GeneratorTestHelper.GetInsertDataExpression();
expression.AdditionalFeatures[PostgresExtensions.OverridingIdentityValues] = PostgresOverridingIdentityValuesType.System;

Should.Throw<NotSupportedException>(() => Generator.Generate(expression));
}

[Test]
public virtual void CanInsertWithOverridingUserValue()
{
var expression = GeneratorTestHelper.GetInsertDataExpression();
expression.AdditionalFeatures[PostgresExtensions.OverridingIdentityValues] = PostgresOverridingIdentityValuesType.User;

Should.Throw<NotSupportedException>(() => Generator.Generate(expression));
}

[Test]
public override void CanUpdateDataForAllDataWithCustomSchema()
{
Expand Down Expand Up @@ -174,5 +195,10 @@ public override void CanUpdateDataWithDbNullCriteria()
var result = Generator.Generate(expression);
result.ShouldBe("UPDATE \"public\".\"TestTable1\" SET \"Name\" = 'Just''in', \"Age\" = 25 WHERE \"Id\" = 9 AND \"Homepage\" IS NULL;");
}

protected virtual PostgresGenerator CreateGenerator(PostgresQuoter quoter)
{
return new PostgresGenerator(quoter);
}
}
}
@@ -0,0 +1,62 @@
#region License
// Copyright (c) 2021, FluentMigrator Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

using FluentMigrator.Postgres;
using FluentMigrator.Runner.Generators.Postgres;
using FluentMigrator.Tests.Unit.Generators.Postgres;

using NUnit.Framework;

using Shouldly;

namespace FluentMigrator.Tests.Unit.Generators.Postgres10_0
{
[TestFixture]
public class Postgres10_0DataTests : PostgresDataTests
{
/// <inheritdoc />
protected override PostgresGenerator CreateGenerator(PostgresQuoter quoter)
{
return new Postgres10_0Generator(quoter);
}

[Test]
public override void CanInsertWithOverridingSystemValue()
{
var expression = GeneratorTestHelper.GetInsertDataExpression();
expression.AdditionalFeatures[PostgresExtensions.OverridingIdentityValues] = PostgresOverridingIdentityValuesType.System;

var expected = "INSERT INTO \"public\".\"TestTable1\" (\"Id\",\"Name\",\"Website\") OVERRIDING SYSTEM VALUE VALUES (1,'Just''in','codethinked.com');";
expected += "INSERT INTO \"public\".\"TestTable1\" (\"Id\",\"Name\",\"Website\") OVERRIDING SYSTEM VALUE VALUES (2,'Na\\te','kohari.org');";

var result = Generator.Generate(expression);
result.ShouldBe(expected);
}

[Test]
public override void CanInsertWithOverridingUserValue()
{
var expression = GeneratorTestHelper.GetInsertDataExpression();
expression.AdditionalFeatures[PostgresExtensions.OverridingIdentityValues] = PostgresOverridingIdentityValuesType.User;

var expected = "INSERT INTO \"public\".\"TestTable1\" (\"Id\",\"Name\",\"Website\") OVERRIDING USER VALUE VALUES (1,'Just''in','codethinked.com');";
expected += "INSERT INTO \"public\".\"TestTable1\" (\"Id\",\"Name\",\"Website\") OVERRIDING USER VALUE VALUES (2,'Na\\te','kohari.org');";

var result = Generator.Generate(expression);
result.ShouldBe(expected);
}
}
}

0 comments on commit a0ebc3e

Please sign in to comment.