diff --git a/src/FluentMigrator.Extensions.Postgres/Postgres/PostgresExtensions.OverridingIdentityValues.cs b/src/FluentMigrator.Extensions.Postgres/Postgres/PostgresExtensions.OverridingIdentityValues.cs new file mode 100644 index 000000000..03b2985a5 --- /dev/null +++ b/src/FluentMigrator.Extensions.Postgres/Postgres/PostgresExtensions.OverridingIdentityValues.cs @@ -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"; + + /// + /// Adds an OVERRIDING SYSTEM VALUE clause in the current expression. + /// This enables the system-generated values to be overriden with the user-specified explicit values (other than DEFAULT) + /// for identity columns defined as GENERATED ALWAYS + /// + /// The current expression + /// The current expression + public static IInsertDataSyntax WithOverridingSystemValue(this IInsertDataSyntax expression) => + SetOverridingIdentityValues(expression, PostgresOverridingIdentityValuesType.System, nameof(WithOverridingSystemValue)); + + /// + /// Adds an OVERRIDING USER VALUE clause in the current expression. + /// Any user-specified values will be ignored and the system-generated values will be applied + /// for identity columns defined as GENERATED BY DEFAULT + /// + /// The current expression + /// The current expression + public static IInsertDataSyntax WithOverridingUserValue(this IInsertDataSyntax expression) => + SetOverridingIdentityValues(expression, PostgresOverridingIdentityValuesType.User, nameof(WithOverridingUserValue)); + + /// + /// Set the additional feature for overriding identity values with the specified + /// on the provided expression + /// + /// + 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; + } + } +} diff --git a/src/FluentMigrator.Extensions.Postgres/PostgresOverridingIdentityValuesType.cs b/src/FluentMigrator.Extensions.Postgres/PostgresOverridingIdentityValuesType.cs new file mode 100644 index 000000000..67cae7d0c --- /dev/null +++ b/src/FluentMigrator.Extensions.Postgres/PostgresOverridingIdentityValuesType.cs @@ -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 +{ + /// + /// Determines if the system-generated values or the user-supplied values are applied for identity columns + /// + public enum PostgresOverridingIdentityValuesType + { + /// + /// Overrides the system-generated values with user-supplied values for identity columns + /// + System, + /// + /// Ignores the user-supplied values and applies the system-generated values for identity columns + /// + User, + } +} diff --git a/src/FluentMigrator.Runner.Postgres/Generators/Postgres/Postgres10_0Generator.cs b/src/FluentMigrator.Runner.Postgres/Generators/Postgres/Postgres10_0Generator.cs index f85e434f7..908f582ac 100644 --- a/src/FluentMigrator.Runner.Postgres/Generators/Postgres/Postgres10_0Generator.cs +++ b/src/FluentMigrator.Runner.Postgres/Generators/Postgres/Postgres10_0Generator.cs @@ -16,6 +16,8 @@ using System.Collections.Generic; +using FluentMigrator.Expressions; +using FluentMigrator.Infrastructure.Extensions; using FluentMigrator.Postgres; using JetBrains.Annotations; @@ -61,5 +63,21 @@ protected override HashSet GetAllowIndexStorageParameters() return allow; } + + /// + protected override string GetOverridingIdentityValuesString(InsertDataExpression expression) + { + if (!expression.AdditionalFeatures.ContainsKey(PostgresExtensions.OverridingIdentityValues)) + { + return string.Empty; + } + + var overridingIdentityValues = + expression.GetAdditionalFeature( + PostgresExtensions.OverridingIdentityValues); + + return string.Format(" OVERRIDING {0} VALUE", + overridingIdentityValues == PostgresOverridingIdentityValuesType.User ? "USER" : "SYSTEM"); + } } } diff --git a/src/FluentMigrator.Runner.Postgres/Generators/Postgres/PostgresGenerator.cs b/src/FluentMigrator.Runner.Postgres/Generators/Postgres/PostgresGenerator.cs index 54b217f4f..a021f7049 100644 --- a/src/FluentMigrator.Runner.Postgres/Generators/Postgres/PostgresGenerator.cs +++ b/src/FluentMigrator.Runner.Postgres/Generators/Postgres/PostgresGenerator.cs @@ -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(); } @@ -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+."); + } } } diff --git a/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs b/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs index 5e2d74464..11d2c4ffc 100644 --- a/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs +++ b/test/FluentMigrator.Tests/Unit/Builders/Insert/InsertDataExpressionBuilderTests.cs @@ -20,6 +20,7 @@ using FluentMigrator.Builders.Insert; using FluentMigrator.Expressions; +using FluentMigrator.Postgres; using FluentMigrator.SqlServer; using NUnit.Framework; @@ -96,5 +97,81 @@ public void SqlServerIdentityInsertCalledTwiceAddsCorrectAdditionalFeature() expression.AdditionalFeatures.ShouldContain( new KeyValuePair(SqlServerExtensions.IdentityInsert, true)); } + + [Test] + public void PostgresOverridingSystemValueAddsCorrectAdditionalFeature() + { + var expression = new InsertDataExpression(); + var builder = new InsertDataExpressionBuilder(expression); + builder.WithOverridingSystemValue(); + + expression.AdditionalFeatures.ShouldContain( + new KeyValuePair( + 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( + PostgresExtensions.OverridingIdentityValues, + PostgresOverridingIdentityValuesType.System)); + } + + [Test] + public void PostgresOverridingUserValueAddsCorrectAdditionalFeature() + { + var expression = new InsertDataExpression(); + var builder = new InsertDataExpressionBuilder(expression); + builder.WithOverridingUserValue(); + + expression.AdditionalFeatures.ShouldContain( + new KeyValuePair( + 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( + 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( + PostgresExtensions.OverridingIdentityValues, + PostgresOverridingIdentityValuesType.User)); + expressionForSystemValue.AdditionalFeatures.ShouldContain( + new KeyValuePair( + PostgresExtensions.OverridingIdentityValues, + PostgresOverridingIdentityValuesType.System)); + } } } diff --git a/test/FluentMigrator.Tests/Unit/Generators/Postgres/PostgresDataTests.cs b/test/FluentMigrator.Tests/Unit/Generators/Postgres/PostgresDataTests.cs index 027c0e8ba..cec7b34ba 100644 --- a/test/FluentMigrator.Tests/Unit/Generators/Postgres/PostgresDataTests.cs +++ b/test/FluentMigrator.Tests/Unit/Generators/Postgres/PostgresDataTests.cs @@ -1,3 +1,6 @@ +using System; + +using FluentMigrator.Postgres; using FluentMigrator.Runner.Generators.Postgres; using FluentMigrator.Runner.Processors.Postgres; @@ -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] @@ -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(() => Generator.Generate(expression)); + } + + [Test] + public virtual void CanInsertWithOverridingUserValue() + { + var expression = GeneratorTestHelper.GetInsertDataExpression(); + expression.AdditionalFeatures[PostgresExtensions.OverridingIdentityValues] = PostgresOverridingIdentityValuesType.User; + + Should.Throw(() => Generator.Generate(expression)); + } + [Test] public override void CanUpdateDataForAllDataWithCustomSchema() { @@ -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); + } } } diff --git a/test/FluentMigrator.Tests/Unit/Generators/Postgres10_0/Postgres10_0DataTests.cs b/test/FluentMigrator.Tests/Unit/Generators/Postgres10_0/Postgres10_0DataTests.cs new file mode 100644 index 000000000..26a1dbc5c --- /dev/null +++ b/test/FluentMigrator.Tests/Unit/Generators/Postgres10_0/Postgres10_0DataTests.cs @@ -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 + { + /// + 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); + } + } +} diff --git a/test/FluentMigrator.Tests/Unit/Generators/Postgres11_0/Postgres11_0DataTests.cs b/test/FluentMigrator.Tests/Unit/Generators/Postgres11_0/Postgres11_0DataTests.cs new file mode 100644 index 000000000..109e25249 --- /dev/null +++ b/test/FluentMigrator.Tests/Unit/Generators/Postgres11_0/Postgres11_0DataTests.cs @@ -0,0 +1,30 @@ +#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.Runner.Generators.Postgres; +using FluentMigrator.Tests.Unit.Generators.Postgres10_0; + +namespace FluentMigrator.Tests.Unit.Generators.Postgres11_0 +{ + public class Postgres11_0DataTests : Postgres10_0DataTests + { + /// + protected override PostgresGenerator CreateGenerator(PostgresQuoter quoter) + { + return new Postgres11_0Generator(quoter); + } + } +}