diff --git a/PetaPoco.Tests.Integration/Databases/DatabaseTests.cs b/PetaPoco.Tests.Integration/Databases/DatabaseTests.cs index 3839f457..3060b1aa 100644 --- a/PetaPoco.Tests.Integration/Databases/DatabaseTests.cs +++ b/PetaPoco.Tests.Integration/Databases/DatabaseTests.cs @@ -36,12 +36,14 @@ protected virtual void AfterDbCreate(Database db) public virtual void Construct_GivenConnection_ShouldBeValid() { var factory = DB.Provider.GetFactory(); + var ignoreCase = DB.Provider.UseOrdinaryIdentifiers; + using (var connection = factory.CreateConnection()) { connection.ConnectionString = DB.ConnectionString; connection.Open(); - using (var db = new Database(connection)) + using (var db = new Database(connection, ignoreCase: ignoreCase)) { AfterDbCreate(db); var key = db.Insert(_note); @@ -57,8 +59,9 @@ public virtual void Construct_GivenConnectionStringAndProviderName_ShouldBeValid { var providerName = ProviderName; var connectionString = DB.ConnectionString; + var ignoreCase = DB.Provider.UseOrdinaryIdentifiers; - using (var db = new Database(connectionString, providerName)) + using (var db = new Database(connectionString, providerName, ignoreCase: ignoreCase)) { AfterDbCreate(db); var key = db.Insert(_note); @@ -73,8 +76,9 @@ public virtual void Construct_GivenConnectionStringAndProviderFactory_ShouldBeVa { var factory = DB.Provider.GetFactory(); var connectionString = DB.ConnectionString; + var ignoreCase = DB.Provider.UseOrdinaryIdentifiers; - using (var db = new Database(connectionString, factory)) + using (var db = new Database(connectionString, factory, ignoreCase: ignoreCase)) { AfterDbCreate(db); var key = db.Insert(_note); @@ -90,8 +94,9 @@ public virtual void Construct_GivenConnectionStringName_ShouldBeValid() { var connectionString = DB.ConnectionString; var entry = ConfigurationManager.ConnectionStrings.Cast().FirstOrDefault(c => c.ConnectionString.Equals(connectionString)); + var ignoreCase = DB.Provider.UseOrdinaryIdentifiers; - using (var db = new Database(entry.Name)) + using (var db = new Database(entry.Name, ignoreCase: ignoreCase)) { AfterDbCreate(db); var key = db.Insert(_note); diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDatabaseTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDatabaseTests.cs index a9ea2f0f..b9081eb7 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDatabaseTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDatabaseTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleDatabaseTests : DatabaseTests + public abstract partial class OracleDatabaseTests : DatabaseTests { - public OracleDatabaseTests() - : base(new OracleTestProvider()) + protected OracleDatabaseTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleDatabaseTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleDatabaseTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDeleteTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDeleteTests.cs index 2ff1a9c7..da6ffc68 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDeleteTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleDeleteTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleDeleteTests : DeleteTests + public abstract partial class OracleDeleteTests : DeleteTests { - public OracleDeleteTests() - : base(new OracleTestProvider()) + protected OracleDeleteTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleDeleteTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleDeleteTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleExecuteTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleExecuteTests.cs index 8e747d91..ea4de56d 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleExecuteTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleExecuteTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleExecuteTests : ExecuteTests + public abstract partial class OracleExecuteTests : ExecuteTests { - public OracleExecuteTests() - : base(new OracleTestProvider()) + protected OracleExecuteTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleExecuteTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleExecuteTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleInsertTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleInsertTests.cs index 9b43b6a1..fd96a488 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleInsertTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleInsertTests.cs @@ -5,12 +5,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleInsertTests : InsertTests + public abstract partial class OracleInsertTests : InsertTests { - public OracleInsertTests() - : base(new OracleTestProvider()) + protected OracleInsertTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleInsertTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleInsertTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleMiscellaneousTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleMiscellaneousTests.cs index 8cc0bb05..20ce859b 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleMiscellaneousTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleMiscellaneousTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleMiscellaneousTests : MiscellaneousTests + public abstract partial class OracleMiscellaneousTests : MiscellaneousTests { - public OracleMiscellaneousTests() - : base(new OracleTestProvider()) + protected OracleMiscellaneousTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleMiscellaneousTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleMiscellaneousTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OraclePreExecuteTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OraclePreExecuteTests.cs index 976906df..ce7f5de7 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OraclePreExecuteTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OraclePreExecuteTests.cs @@ -6,36 +6,79 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OraclePreExecuteTests : PreExecuteTests + public abstract partial class OraclePreExecuteTests : PreExecuteTests { - protected override IPreExecuteDatabaseProvider Provider => DB.Provider as PreExecuteDatabaseProvider; - - public OraclePreExecuteTests() - : base(new PreExecuteTestProvider()) + protected OraclePreExecuteTests(TestProvider provider) + : base(provider) { - Provider.ThrowExceptions = true; } - protected class PreExecuteTestProvider : OracleTestProvider + [Collection("Oracle.Delimited")] + public class Delimited : OraclePreExecuteTests { - protected override IDatabase LoadFromConnectionName(string name) - => BuildFromConnectionName(name).UsingProvider().Create(); + protected override IPreExecuteDatabaseProvider Provider => DB.Provider as PreExecuteDatabaseProvider; + + public Delimited() + : base(new PreExecuteTestProvider()) + { + Provider.ThrowExceptions = true; + } + + protected class PreExecuteTestProvider : OracleDelimitedTestProvider + { + protected override IDatabase LoadFromConnectionName(string name) + => BuildFromConnectionName(name).UsingProvider().Create(); + } + + protected class PreExecuteDatabaseProvider : PetaPoco.Providers.OracleDatabaseProvider, IPreExecuteDatabaseProvider + { + public bool ThrowExceptions { get; set; } + public List Parameters { get; set; } = new List(); + + public override void PreExecute(IDbCommand cmd) + { + Parameters.Clear(); + + if (ThrowExceptions) + { + Parameters = cmd.Parameters.Cast().ToList(); + throw new PreExecuteException(); + } + } + } } - protected class PreExecuteDatabaseProvider : PetaPoco.Providers.OracleDatabaseProvider, IPreExecuteDatabaseProvider + [Collection("Oracle.Ordinary")] + public class Ordinary : OraclePreExecuteTests { - public bool ThrowExceptions { get; set; } - public List Parameters { get; set; } = new List(); + protected override IPreExecuteDatabaseProvider Provider => DB.Provider as PreExecuteDatabaseProvider; + + public Ordinary() + : base(new PreExecuteTestProvider()) + { + Provider.ThrowExceptions = true; + } - public override void PreExecute(IDbCommand cmd) + protected class PreExecuteTestProvider : OracleOrdinaryTestProvider { - Parameters.Clear(); + protected override IDatabase LoadFromConnectionName(string name) + => BuildFromConnectionName(name).UsingProvider().Create(); + } - if (ThrowExceptions) + protected class PreExecuteDatabaseProvider : PetaPoco.Providers.OracleOrdinaryDatabaseProvider, IPreExecuteDatabaseProvider + { + public bool ThrowExceptions { get; set; } + public List Parameters { get; set; } = new List(); + + public override void PreExecute(IDbCommand cmd) { - Parameters = cmd.Parameters.Cast().ToList(); - throw new PreExecuteException(); + Parameters.Clear(); + + if (ThrowExceptions) + { + Parameters = cmd.Parameters.Cast().ToList(); + throw new PreExecuteException(); + } } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryLinqTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryLinqTests.cs index 1e7db334..67090119 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryLinqTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryLinqTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleQueryLinqTests : QueryLinqTests + public abstract partial class OracleQueryLinqTests : QueryLinqTests { - public OracleQueryLinqTests() - : base(new OracleTestProvider()) + protected OracleQueryLinqTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleQueryLinqTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleQueryLinqTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryTests.cs index 18c01da9..2de34e3c 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleQueryTests.cs @@ -10,14 +10,31 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleQueryTests : QueryTests + public abstract partial class OracleQueryTests : QueryTests { - public OracleQueryTests() - : base(new OracleTestProvider()) + protected OracleQueryTests(TestProvider provider) + : base(provider) { } + [Collection("Oracle.Delimited")] + public class Delimited : OracleQueryTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleQueryTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } + [Fact] public override async Task FetchAsyncWithPaging_ForDynamicTypeGivenSql_ShouldReturnValidDynamicTypeCollection() { diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleStoredProcTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleStoredProcTests.cs index 1cf298ef..8ee59f9a 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleStoredProcTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleStoredProcTests.cs @@ -11,99 +11,138 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleStoredProcTests : StoredProcTests + public abstract partial class OracleStoredProcTests : StoredProcTests { protected override Type DataParameterType => typeof(OracleParameter); - public OracleStoredProcTests() - : base(new OracleTestProvider()) + + protected OracleStoredProcTests(TestProvider provider) + : base(provider) { } + [Collection("Oracle.Delimited")] + public class Delimited : OracleStoredProcTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleStoredProcTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } + private IDataParameter GetOutputParameter() => new OracleParameter("p_out_cursor", OracleDbType.RefCursor, ParameterDirection.Output); [Fact] public override void QueryProc_NoParam_ShouldReturnAll() { - var results = DB.QueryProc("SelectPeople", GetOutputParameter()).ToArray(); + var results = DB.QueryProc(DB.Provider.EscapeTableName("SelectPeople"), GetOutputParameter()).ToArray(); results.Length.ShouldBe(6); } [Fact] public override void QueryProc_WithParam_ShouldReturnSome() { - var results = DB.QueryProc("SelectPeopleWithParam", new { age = 20 }, GetOutputParameter()).ToArray(); + var results = DB.QueryProc(DB.Provider.EscapeTableName("SelectPeopleWithParam"), new { age = 20 }, GetOutputParameter()).ToArray(); results.Length.ShouldBe(3); } [Fact] public override void QueryProc_WithDbParam_ShouldReturnSome() { - var results = DB.QueryProc("SelectPeopleWithParam", GetDataParameter(), GetOutputParameter()).ToArray(); + var results = DB.QueryProc(DB.Provider.EscapeTableName("SelectPeopleWithParam"), GetDataParameter(), GetOutputParameter()).ToArray(); results.Length.ShouldBe(3); } [Fact] public override void FetchProc_NoParam_ShouldReturnAll() { - var results = DB.FetchProc("SelectPeople", GetOutputParameter()); + var results = DB.FetchProc(DB.Provider.EscapeTableName("SelectPeople"), GetOutputParameter()); results.Count.ShouldBe(6); } [Fact] public override void FetchProc_WithParam_ShouldReturnSome() { - var results = DB.FetchProc("SelectPeopleWithParam", new { age = 20 }, GetOutputParameter()); + var results = DB.FetchProc(DB.Provider.EscapeTableName("SelectPeopleWithParam"), new { age = 20 }, GetOutputParameter()); results.Count.ShouldBe(3); } [Fact] public override void FetchProc_WithDbParam_ShouldReturnSome() { - var results = DB.FetchProc("SelectPeopleWithParam", GetDataParameter(), GetOutputParameter()); + var results = DB.FetchProc(DB.Provider.EscapeTableName("SelectPeopleWithParam"), GetDataParameter(), GetOutputParameter()); results.Count.ShouldBe(3); } [Fact] public override void ScalarProc_NoParam_ShouldReturnAll() { - var count = DB.ExecuteScalarProc("CountPeople", GetOutputParameter()); + var count = DB.ExecuteScalarProc(DB.Provider.EscapeTableName("CountPeople"), GetOutputParameter()); count.ShouldBe(6); } [Fact] public override void ScalarProc_WithParam_ShouldReturnSome() { - var count = DB.ExecuteScalarProc("CountPeopleWithParam", new { age = 20 }, GetOutputParameter()); + var count = DB.ExecuteScalarProc(DB.Provider.EscapeTableName("CountPeopleWithParam"), new { age = 20 }, GetOutputParameter()); count.ShouldBe(3); } [Fact] public override void ScalarProc_WithDbParam_ShouldReturnSome() { - var count = DB.ExecuteScalarProc("CountPeopleWithParam", GetDataParameter(), GetOutputParameter()); + var count = DB.ExecuteScalarProc(DB.Provider.EscapeTableName("CountPeopleWithParam"), GetDataParameter(), GetOutputParameter()); count.ShouldBe(3); } [Fact] public override void NonQueryProc_NoParam_ShouldUpdateAll() { - DB.ExecuteNonQueryProc("UpdatePeople"); + DB.ExecuteNonQueryProc(DB.Provider.EscapeTableName("UpdatePeople")); DB.Query($"WHERE {DB.Provider.EscapeSqlIdentifier("FullName")}='Updated'").Count().ShouldBe(6); } [Fact] public override void NonQueryProc_WithParam_ShouldUpdateSome() { - DB.ExecuteNonQueryProc("UpdatePeopleWithParam", new { age = 20 }); + DB.ExecuteNonQueryProc(DB.Provider.EscapeTableName("UpdatePeopleWithParam"), new { age = 20 }); DB.Query($"WHERE {DB.Provider.EscapeSqlIdentifier("FullName")}='Updated'").Count().ShouldBe(3); } [Fact] public override void NonQueryProc_WithDbParam_ShouldUpdateSome() { - DB.ExecuteNonQueryProc("UpdatePeopleWithParam", GetDataParameter()); + DB.ExecuteNonQueryProc(DB.Provider.EscapeTableName("UpdatePeopleWithParam"), GetDataParameter()); + DB.Query($"WHERE {DB.Provider.EscapeSqlIdentifier("FullName")}='Updated'").Count().ShouldBe(3); + } + + [Fact] + public override async Task NonQueryProcAsync_NoParam_ShouldUpdateAll() + { + await DB.ExecuteNonQueryProcAsync(DB.Provider.EscapeSqlIdentifier("UpdatePeople")); + DB.Query($"WHERE {DB.Provider.EscapeSqlIdentifier("FullName")}='Updated'").Count().ShouldBe(6); + } + + [Fact] + public override async Task NonQueryProcAsync_WithParam_ShouldUpdateSome() + { + await DB.ExecuteNonQueryProcAsync(DB.Provider.EscapeTableName("UpdatePeopleWithParam"), new { age = 20 }); + DB.Query($"WHERE {DB.Provider.EscapeSqlIdentifier("FullName")}='Updated'").Count().ShouldBe(3); + } + + [Fact] + public override async Task NonQueryProcAsync_WithDbParam_ShouldUpdateSome() + { + await DB.ExecuteNonQueryProcAsync(DB.Provider.EscapeTableName("UpdatePeopleWithParam"), GetDataParameter()); DB.Query($"WHERE {DB.Provider.EscapeSqlIdentifier("FullName")}='Updated'").Count().ShouldBe(3); } @@ -111,7 +150,7 @@ public override void NonQueryProc_WithDbParam_ShouldUpdateSome() public override async Task QueryProcAsync_NoParam_ShouldReturnAll() { var results = new List(); - await DB.QueryProcAsync(p => results.Add(p), "SelectPeople", GetOutputParameter()); + await DB.QueryProcAsync(p => results.Add(p), DB.Provider.EscapeTableName("SelectPeople"), GetOutputParameter()); results.Count.ShouldBe(6); } @@ -119,7 +158,7 @@ public override async Task QueryProcAsync_NoParam_ShouldReturnAll() public override async Task QueryProcAsync_WithParam_ShouldReturnSome() { var results = new List(); - await DB.QueryProcAsync(p => results.Add(p), "SelectPeopleWithParam", new { age = 20 }, GetOutputParameter()); + await DB.QueryProcAsync(p => results.Add(p), DB.Provider.EscapeTableName("SelectPeopleWithParam"), new { age = 20 }, GetOutputParameter()); results.Count.ShouldBe(3); } @@ -127,7 +166,7 @@ public override async Task QueryProcAsync_WithParam_ShouldReturnSome() public override async Task QueryProcAsync_WithDbParam_ShouldReturnSome() { var results = new List(); - await DB.QueryProcAsync(p => results.Add(p), "SelectPeopleWithParam", GetDataParameter(), GetOutputParameter()); + await DB.QueryProcAsync(p => results.Add(p), DB.Provider.EscapeTableName("SelectPeopleWithParam"), GetDataParameter(), GetOutputParameter()); results.Count.ShouldBe(3); } @@ -135,7 +174,7 @@ public override async Task QueryProcAsync_WithDbParam_ShouldReturnSome() public override async Task QueryProcAsyncReader_NoParam_ShouldReturnAll() { var results = new List(); - using (var reader = await DB.QueryProcAsync("SelectPeople", GetOutputParameter())) + using (var reader = await DB.QueryProcAsync(DB.Provider.EscapeTableName("SelectPeople"), GetOutputParameter())) { while (await reader.ReadAsync()) results.Add(reader.Poco); @@ -147,7 +186,7 @@ public override async Task QueryProcAsyncReader_NoParam_ShouldReturnAll() public override async Task QueryProcAsyncReader_WithParam_ShouldReturnSome() { var results = new List(); - using (var reader = await DB.QueryProcAsync("SelectPeopleWithParam", new { age = 20 }, GetOutputParameter())) + using (var reader = await DB.QueryProcAsync(DB.Provider.EscapeTableName("SelectPeopleWithParam"), new { age = 20 }, GetOutputParameter())) { while (await reader.ReadAsync()) results.Add(reader.Poco); @@ -159,7 +198,7 @@ public override async Task QueryProcAsyncReader_WithParam_ShouldReturnSome() public override async Task QueryProcAsyncReader_WithDbParam_ShouldReturnSome() { var results = new List(); - using (var reader = await DB.QueryProcAsync("SelectPeopleWithParam", GetDataParameter(), GetOutputParameter())) + using (var reader = await DB.QueryProcAsync(DB.Provider.EscapeTableName("SelectPeopleWithParam"), GetDataParameter(), GetOutputParameter())) { while (await reader.ReadAsync()) results.Add(reader.Poco); @@ -170,42 +209,42 @@ public override async Task QueryProcAsyncReader_WithDbParam_ShouldReturnSome() [Fact] public override async Task FetchProcAsync_NoParam_ShouldReturnAll() { - var results = await DB.FetchProcAsync("SelectPeople", GetOutputParameter()); + var results = await DB.FetchProcAsync(DB.Provider.EscapeTableName("SelectPeople"), GetOutputParameter()); results.Count.ShouldBe(6); } [Fact] public override async Task FetchProcAsync_WithParam_ShouldReturnSome() { - var results = await DB.FetchProcAsync("SelectPeopleWithParam", new { age = 20 }, GetOutputParameter()); + var results = await DB.FetchProcAsync(DB.Provider.EscapeTableName("SelectPeopleWithParam"), new { age = 20 }, GetOutputParameter()); results.Count.ShouldBe(3); } [Fact] public override async Task FetchProcAsync_WithDbParam_ShouldReturnSome() { - var results = await DB.FetchProcAsync("SelectPeopleWithParam", GetDataParameter(), GetOutputParameter()); + var results = await DB.FetchProcAsync(DB.Provider.EscapeTableName("SelectPeopleWithParam"), GetDataParameter(), GetOutputParameter()); results.Count.ShouldBe(3); } [Fact] public override async Task ScalarProcAsync_NoParam_ShouldReturnAll() { - var count = await DB.ExecuteScalarProcAsync("CountPeople", GetOutputParameter()); + var count = await DB.ExecuteScalarProcAsync(DB.Provider.EscapeTableName("CountPeople"), GetOutputParameter()); count.ShouldBe(6); } [Fact] public override async Task ScalarProcAsync_WithParam_ShouldReturnSome() { - var count = await DB.ExecuteScalarProcAsync("CountPeopleWithParam", new { age = 20 }, GetOutputParameter()); + var count = await DB.ExecuteScalarProcAsync(DB.Provider.EscapeTableName("CountPeopleWithParam"), new { age = 20 }, GetOutputParameter()); count.ShouldBe(3); } [Fact] public override async Task ScalarProcAsync_WithDbParam_ShouldReturnSome() { - var count = await DB.ExecuteScalarProcAsync("CountPeopleWithParam", GetDataParameter(), GetOutputParameter()); + var count = await DB.ExecuteScalarProcAsync(DB.Provider.EscapeTableName("CountPeopleWithParam"), GetDataParameter(), GetOutputParameter()); count.ShouldBe(3); } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleTriageTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleTriageTests.cs index 9d7617ed..747b98a3 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleTriageTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleTriageTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleTriageTests : TriageTests + public abstract partial class OracleTriageTests : TriageTests { - public OracleTriageTests() - : base(new OracleTestProvider()) + protected OracleTriageTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleTriageTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleTriageTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleUpdateTests.cs b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleUpdateTests.cs index 914549a0..ad6ece1c 100644 --- a/PetaPoco.Tests.Integration/Databases/OracleTests/OracleUpdateTests.cs +++ b/PetaPoco.Tests.Integration/Databases/OracleTests/OracleUpdateTests.cs @@ -3,12 +3,29 @@ namespace PetaPoco.Tests.Integration.Databases.Oracle { - [Collection("Oracle")] - public class OracleUpdateTests : UpdateTests + public abstract partial class OracleUpdateTests : UpdateTests { - public OracleUpdateTests() - : base(new OracleTestProvider()) + protected OracleUpdateTests(TestProvider provider) + : base(provider) { } + + [Collection("Oracle.Delimited")] + public class Delimited : OracleUpdateTests + { + public Delimited() + : base(new OracleDelimitedTestProvider()) + { + } + } + + [Collection("Oracle.Ordinary")] + public class Ordinary : OracleUpdateTests + { + public Ordinary() + : base(new OracleOrdinaryTestProvider()) + { + } + } } } diff --git a/PetaPoco.Tests.Integration/PetaPoco.Tests.Integration.csproj b/PetaPoco.Tests.Integration/PetaPoco.Tests.Integration.csproj index 4dec1f97..0696e830 100644 --- a/PetaPoco.Tests.Integration/PetaPoco.Tests.Integration.csproj +++ b/PetaPoco.Tests.Integration/PetaPoco.Tests.Integration.csproj @@ -22,8 +22,10 @@ - - + + + + @@ -35,8 +37,10 @@ - - + + + + diff --git a/PetaPoco.Tests.Integration/Providers/OracleTestProvider.cs b/PetaPoco.Tests.Integration/Providers/OracleTestProvider.cs index 0a42aebb..0be54513 100644 --- a/PetaPoco.Tests.Integration/Providers/OracleTestProvider.cs +++ b/PetaPoco.Tests.Integration/Providers/OracleTestProvider.cs @@ -9,20 +9,22 @@ namespace PetaPoco.Tests.Integration.Providers { - public class OracleTestProvider : TestProvider + public abstract class OracleTestProvider : TestProvider { private static readonly string[] _splitSemiColon = new[] { ";" }; private static readonly string[] _splitNewLine = new[] { Environment.NewLine }; private static readonly string[] _splitSlash = new[] { Environment.NewLine + "/" }; private static readonly string[] _resources = new[] { - "PetaPoco.Tests.Integration.Scripts.OracleSetupDatabase.sql", - "PetaPoco.Tests.Integration.Scripts.OracleBuildDatabase.sql" + "PetaPoco.Tests.Integration.Scripts.OracleSetupDatabase", + "PetaPoco.Tests.Integration.Scripts.OracleBuildDatabase" }; private static volatile Exception _setupException; private static ExecutionPhase _phase = ExecutionPhase.Setup; - private string _connectionName = "Oracle"; + protected OracleTestProvider(string connectionName) => _connectionName = $"Oracle_{connectionName}"; + + private string _connectionName; protected override string ConnectionName => _connectionName; protected override string ScriptResourceName => _resources[(int)_phase]; @@ -103,4 +105,18 @@ private string StripLineComments(string script) return string.Join(_splitNewLine[0], parts); } } + + public class OracleDelimitedTestProvider : OracleTestProvider + { + public OracleDelimitedTestProvider() : base("Delimited") { } + protected override string ScriptResourceName => $"{base.ScriptResourceName}Delimited.sql"; + } + + public class OracleOrdinaryTestProvider : OracleTestProvider + { + public OracleOrdinaryTestProvider() : base("Ordinary") { } + protected override IDatabase LoadFromConnectionName(string name) + => BuildFromConnectionName(name).UsingProvider().Create(); + protected override string ScriptResourceName => $"{base.ScriptResourceName}Ordinary.sql"; + } } diff --git a/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabaseDelimited.sql b/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabaseDelimited.sql new file mode 100644 index 00000000..7760a442 --- /dev/null +++ b/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabaseDelimited.sql @@ -0,0 +1,190 @@ +-- Case Sensitive version of OracleBuildDatabaseOrdinary.sql +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."OrderLines"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."Orders"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."People"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."SpecificOrderLines"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."SpecificOrders"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."SpecificPeople"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."TransactionLogs"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."Note"'); +/ + +CREATE TABLE "People" ( + "Id" VARCHAR2(36) NOT NULL, + "FullName" VARCHAR2(255), + "Age" NUMBER(19) NOT NULL, + "Height" NUMBER(10) NOT NULL, + "Dob" TIMESTAMP NULL, + PRIMARY KEY("Id") +); + +CREATE TABLE "Orders" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "PersonId" VARCHAR2(36), + "PoNumber" VARCHAR2(15) NOT NULL, + "OrderStatus" NUMBER(10) NOT NULL, + "CreatedOn" TIMESTAMP NOT NULL, + "CreatedBy" VARCHAR2(255) NOT NULL, + PRIMARY KEY("Id"), + FOREIGN KEY ("PersonId") REFERENCES "People"("Id") +); + +CREATE TABLE "OrderLines" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "OrderId" NUMBER(10) NOT NULL, + "Qty" NUMBER(5) NOT NULL, + "Status" NUMBER(3) NOT NULL, + "SellPrice" NUMERIC(10, 4) NOT NULL, + PRIMARY KEY("Id"), + FOREIGN KEY ("OrderId") REFERENCES "Orders"("Id") +); + +CREATE TABLE "SpecificPeople" ( + "Id" VARCHAR2(36) NOT NULL, + "FullName" VARCHAR2(255), + "Age" NUMBER(19) NOT NULL, + "Height" NUMBER(10) NOT NULL, + "Dob" TIMESTAMP NULL, + PRIMARY KEY("Id") +); + +CREATE TABLE "SpecificOrders" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "PersonId" VARCHAR2(36), + "PoNumber" VARCHAR2(15) NOT NULL, + "OrderStatus" NUMBER(10) NOT NULL, + "CreatedOn" TIMESTAMP NOT NULL, + "CreatedBy" VARCHAR2(255) NOT NULL, + PRIMARY KEY("Id"), + FOREIGN KEY("PersonId") REFERENCES "SpecificPeople"("Id") +); + +CREATE TABLE "SpecificOrderLines" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "OrderId" NUMBER(10) NOT NULL, + "Qty" NUMBER(5) NOT NULL, + "Status" NUMBER(3) NOT NULL, + "SellPrice" NUMBER(10, 4) NOT NULL, + PRIMARY KEY("Id"), + FOREIGN KEY("OrderId") REFERENCES "SpecificOrders"("Id") +); + +CREATE TABLE "TransactionLogs" ( + "Description" LONG, + "CreatedOn" TIMESTAMP NOT NULL +); + +CREATE TABLE "Note" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "Text" LONG NOT NULL, + "CreatedOn" TIMESTAMP NOT NULL +); +/ + +-- Investigation Tables +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."BugInvestigation_10R9LZYK"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."BugInvestigation_3F489XV0"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."BugInvestigation_64O6LT8U"'); +/ +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_delimited."BugInvestigation_5TN5C4U4"'); +/ + +CREATE TABLE "BugInvestigation_10R9LZYK" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "TestColumn1" LONG RAW, + PRIMARY KEY("Id") +); +/ + +CREATE TABLE "BugInvestigation_3F489XV0" ( + "Id" NUMBER(10) GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + "TC1" NUMBER(10) NOT NULL, + "TC2" NUMBER(10) NOT NULL, + "TC3" NUMBER(10) NOT NULL, + "TC4" NUMBER(10) NOT NULL, + PRIMARY KEY("Id") +); +/ + +CREATE TABLE "BugInvestigation_64O6LT8U" ( + "ColumnA" VARCHAR2(20), + "Column2" VARCHAR2(20) +); +/ + +CREATE TABLE "BugInvestigation_5TN5C4U4" ( + "ColumnA" VARCHAR2(20), + "Column2" VARCHAR2(20) +); +/ + +-- Stored procedures +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_delimited."SelectPeople"'); +/ +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_delimited."SelectPeopleWithParam"'); +/ +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_delimited."CountPeople"'); +/ +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_delimited."CountPeopleWithParam"'); +/ +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_delimited."UpdatePeople"'); +/ +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_delimited."UpdatePeopleWithParam"'); +/ + +CREATE PROCEDURE "SelectPeople" + ("p_out_cursor" OUT SYS_REFCURSOR) AS +BEGIN + OPEN "p_out_cursor" FOR + SELECT * FROM "People"; +END; +/ + +CREATE PROCEDURE "SelectPeopleWithParam" + ("age" IN NUMERIC DEFAULT 0, + "p_out_cursor" OUT SYS_REFCURSOR) AS +BEGIN + OPEN "p_out_cursor" FOR + SELECT * FROM "People" WHERE "Age" > "SelectPeopleWithParam"."age"; +END; +/ + +CREATE PROCEDURE "CountPeople" + ("p_out_cursor" OUT SYS_REFCURSOR) +AS +BEGIN + OPEN "p_out_cursor" FOR + SELECT COUNT(*) FROM "People"; +END; +/ + +CREATE PROCEDURE "CountPeopleWithParam" + ("age" IN NUMERIC DEFAULT 0, + "p_out_cursor" OUT SYS_REFCURSOR) AS +BEGIN + OPEN "p_out_cursor" FOR + SELECT COUNT(*) FROM "People" WHERE "Age" > "CountPeopleWithParam"."age"; +END; +/ + +CREATE PROCEDURE "UpdatePeople" AS +BEGIN + UPDATE "People" SET "FullName" = 'Updated'; +END; +/ + +CREATE PROCEDURE "UpdatePeopleWithParam" + ("age" IN NUMERIC DEFAULT 0) AS +BEGIN + UPDATE "People" SET "FullName" = 'Updated' WHERE "Age" > "UpdatePeopleWithParam"."age"; +END; +/ diff --git a/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabase.sql b/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabaseOrdinary.sql similarity index 71% rename from PetaPoco.Tests.Integration/Scripts/OracleBuildDatabase.sql rename to PetaPoco.Tests.Integration/Scripts/OracleBuildDatabaseOrdinary.sql index 673dfe36..1f015765 100644 --- a/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabase.sql +++ b/PetaPoco.Tests.Integration/Scripts/OracleBuildDatabaseOrdinary.sql @@ -1,18 +1,19 @@ -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.OrderLines'); +-- Case Insensitive version of OracleBuildDatabaseDelimited.sql +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.OrderLines'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.Orders'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.Orders'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.People'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.People'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.SpecificOrderLines'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.SpecificOrderLines'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.SpecificOrders'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.SpecificOrders'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.SpecificPeople'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.SpecificPeople'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.TransactionLogs'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.TransactionLogs'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.Note'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.Note'); / CREATE TABLE People ( @@ -88,13 +89,13 @@ CREATE TABLE Note ( / -- Investigation Tables -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.BugInvestigation_10R9LZYK'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.BugInvestigation_10R9LZYK'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.BugInvestigation_3F489XV0'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.BugInvestigation_3F489XV0'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.BugInvestigation_64O6LT8U'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.BugInvestigation_64O6LT8U'); / -CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco.BugInvestigation_5TN5C4U4'); +CALL SYS.DROP_IF_EXISTS('TABLE', 'petapoco_ordinary.BugInvestigation_5TN5C4U4'); / CREATE TABLE BugInvestigation_10R9LZYK ( @@ -127,17 +128,17 @@ CREATE TABLE BugInvestigation_5TN5C4U4 ( / -- Stored procedures -CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco.SelectPeople'); +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_ordinary.SelectPeople'); / -CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco.SelectPeopleWithParam'); +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_ordinary.SelectPeopleWithParam'); / -CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco.CountPeople'); +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_ordinary.CountPeople'); / -CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco.CountPeopleWithParam'); +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_ordinary.CountPeopleWithParam'); / -CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco.UpdatePeople'); +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_ordinary.UpdatePeople'); / -CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco.UpdatePeopleWithParam'); +CALL SYS.DROP_IF_EXISTS('PROCEDURE', 'petapoco_ordinary.UpdatePeopleWithParam'); / CREATE PROCEDURE SelectPeople diff --git a/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabaseDelimited.sql b/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabaseDelimited.sql new file mode 100644 index 00000000..2be15cb5 --- /dev/null +++ b/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabaseDelimited.sql @@ -0,0 +1,27 @@ +-- Drop PETAPOCO_ORDINARY user COMPLETELY (if it exists) +DECLARE + found number := 0; +BEGIN + SELECT COUNT(*) INTO found + FROM all_users + WHERE username = 'PETAPOCO_DELIMITED'; + + IF found <> 0 THEN + BEGIN + EXECUTE IMMEDIATE 'DROP USER petapoco_delimited CASCADE'; + END; + END IF; +END; +/ + +-- Create fresh user +CREATE USER petapoco_delimited IDENTIFIED BY petapoco; + +-- Ensure that the data tablespace is the default for the user. This tablespace will be used when creating tables for example +ALTER USER petapoco_delimited DEFAULT TABLESPACE data_ts; +-- Give user quota e.g. to perform inserts +ALTER USER petapoco_delimited QUOTA UNLIMITED ON data_ts; + +-- Grant the application developer role +GRANT app_dev_role TO petapoco_delimited; +/ diff --git a/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabaseOrdinary.sql b/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabaseOrdinary.sql new file mode 100644 index 00000000..0a6d9877 --- /dev/null +++ b/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabaseOrdinary.sql @@ -0,0 +1,27 @@ +-- Drop PETAPOCO_ORDINARY user COMPLETELY (if it exists) +DECLARE + found number := 0; +BEGIN + SELECT COUNT(*) INTO found + FROM all_users + WHERE username = 'PETAPOCO_ORDINARY'; + + IF found <> 0 THEN + BEGIN + EXECUTE IMMEDIATE 'DROP USER petapoco_ordinary CASCADE'; + END; + END IF; +END; +/ + +-- Create fresh user +CREATE USER petapoco_ordinary IDENTIFIED BY petapoco; + +-- Ensure that the data tablespace is the default for the user. This tablespace will be used when creating tables for example +ALTER USER petapoco_ordinary DEFAULT TABLESPACE data_ts; +-- Give user quota e.g. to perform inserts +ALTER USER petapoco_ordinary QUOTA UNLIMITED ON data_ts; + +-- Grant the application developer role +GRANT app_dev_role TO petapoco_ordinary; +/ diff --git a/PetaPoco.Tests.Integration/app.config b/PetaPoco.Tests.Integration/app.config index 9e5db441..5f81dd95 100644 --- a/PetaPoco.Tests.Integration/app.config +++ b/PetaPoco.Tests.Integration/app.config @@ -13,7 +13,8 @@ - + + diff --git a/PetaPoco.Tests.Integration/appsettings.json b/PetaPoco.Tests.Integration/appsettings.json index 4f52c081..0142cb12 100644 --- a/PetaPoco.Tests.Integration/appsettings.json +++ b/PetaPoco.Tests.Integration/appsettings.json @@ -52,8 +52,13 @@ "ProviderName": "Oracle.ManagedDataAccess" }, { - "Name": "Oracle", - "ConnectionString": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=5008))(CONNECT_DATA=(SERVICE_NAME=FREEPDB1)));User Id=petapoco;Password=petapoco;", + "Name": "Oracle_Delimited", + "ConnectionString": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=5008))(CONNECT_DATA=(SERVICE_NAME=FREEPDB1)));User Id=petapoco_delimited;Password=petapoco;", + "ProviderName": "Oracle.ManagedDataAccess" + }, + { + "Name": "Oracle_Ordinary", + "ConnectionString": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=5008))(CONNECT_DATA=(SERVICE_NAME=FREEPDB1)));User Id=petapoco_ordinary;Password=petapoco;", "ProviderName": "Oracle.ManagedDataAccess" }, { diff --git a/PetaPoco/Core/DatabaseProvider.cs b/PetaPoco/Core/DatabaseProvider.cs index 6485a169..a952c21a 100644 --- a/PetaPoco/Core/DatabaseProvider.cs +++ b/PetaPoco/Core/DatabaseProvider.cs @@ -28,6 +28,9 @@ public abstract class DatabaseProvider : IProvider /// public abstract DbProviderFactory GetFactory(); + /// + public virtual bool UseOrdinaryIdentifiers => false; + /// public virtual bool HasNativeGuidSupport => false; @@ -143,9 +146,10 @@ private static IProvider GetCustomProvider(string name) /// Specifies whether to allow the default to be returned if no /// matching provider is found. /// The connection string. + /// Whether to use case insensitive dynamic objects. /// The resolved database provider. /// The name cannot be matched to a provider. - internal static IProvider Resolve(Type providerType, bool allowDefault, string connectionString) + internal static IProvider Resolve(Type providerType, bool allowDefault, string connectionString, bool ignoreCase = false) { var typeName = providerType.Name; @@ -180,7 +184,9 @@ internal static IProvider Resolve(Type providerType, bool allowDefault, string c return Singleton.Instance; if (typeName.StartsWith("Oracle")) - return Singleton.Instance; + return ignoreCase ? + Singleton.Instance : + Singleton.Instance; if (typeName.StartsWith("SQLite") || typeName.StartsWith("Sqlite")) return Singleton.Instance; @@ -209,9 +215,10 @@ internal static IProvider Resolve(Type providerType, bool allowDefault, string c /// Specifies whether to allow the default to be returned if no /// matching provider is found. /// The connection string. + /// Whether to use case insensitive dynamic objects. /// The resolved database provider. /// The name cannot be matched to a provider. - internal static IProvider Resolve(string providerName, bool allowDefault, string connectionString) + internal static IProvider Resolve(string providerName, bool allowDefault, string connectionString, bool ignoreCase = false) { // Try again with provider name var custom = GetCustomProvider(providerName); @@ -243,7 +250,9 @@ internal static IProvider Resolve(string providerName, bool allowDefault, string return Singleton.Instance; if (providerName.IndexOf("Oracle", StringComparison.InvariantCultureIgnoreCase) >= 0) - return Singleton.Instance; + return ignoreCase ? + Singleton.Instance : + Singleton.Instance; if (providerName.IndexOf("SQLite", StringComparison.InvariantCultureIgnoreCase) >= 0) return Singleton.Instance; diff --git a/PetaPoco/Core/ExpandoPoco.cs b/PetaPoco/Core/ExpandoPoco.cs new file mode 100644 index 00000000..fd79c09d --- /dev/null +++ b/PetaPoco/Core/ExpandoPoco.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; + +namespace PetaPoco.Core +{ + /// + /// A basic, case insensitive, implementation of ExpandObject. + /// Should be drop-in replacement for ExpandObject with case insensitive member matching by default. + /// + public sealed class ExpandoPoco : DynamicObject, IDictionary + { + private readonly IDictionary _dictionary; + + /// + /// Initialize a new that does not have members. + /// + /// Whether the instance must be case insensitive or not. Default is + public ExpandoPoco(bool ignoreCase = true) + { + var comparer = ignoreCase ? + (IEqualityComparer)StringComparer.OrdinalIgnoreCase : + EqualityComparer.Default; + _dictionary = new Dictionary(comparer); + } + + #region IDictionary Implementation + + /// + public object this[string key] { get => _dictionary[key]; set => _dictionary[key] = value; } + /// + public ICollection Keys => _dictionary.Keys; + /// + public ICollection Values => _dictionary.Values; + /// + public int Count => _dictionary.Count; + /// + public bool IsReadOnly => _dictionary.IsReadOnly; + /// + public void Add(string key, object value) => _dictionary.Add(key, value); + /// + public void Add(KeyValuePair item) => _dictionary.Add(item); + /// + public void Clear() => _dictionary.Clear(); + /// + public bool Contains(KeyValuePair item) => _dictionary.Contains(item); + /// + public bool ContainsKey(string key) => _dictionary.ContainsKey(key); + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) => _dictionary.CopyTo(array, arrayIndex); + /// + public IEnumerator> GetEnumerator() => _dictionary.GetEnumerator(); + /// + public bool Remove(string key) => _dictionary.Remove(key); + /// + public bool Remove(KeyValuePair item) => _dictionary.Remove(item); + /// + public bool TryGetValue(string key, out object value) => _dictionary.TryGetValue(key, out value); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator(); + #endregion + + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) => + _dictionary.TryGetValue(binder.Name, out result); + + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + _dictionary[binder.Name] = value; + return true; + } + + /// + /// Make castable to + /// + /// + public static implicit operator ExpandoObject(ExpandoPoco obj) + { + var expando = new ExpandoObject(); + var expandoDict = (IDictionary)expando; + + foreach (var entry in obj._dictionary) + { + expandoDict[entry.Key] = entry.Value; + } + + return expando; + } + + /// + /// Make castable to , but case insensitive + /// + /// + public static implicit operator ExpandoPoco(ExpandoObject obj) + { + var expando = new ExpandoPoco(true); + var expandoDict = (IDictionary)expando; + + foreach (var entry in (IDictionary)obj) + { + expandoDict[entry.Key] = entry.Value; + } + + return expando; + } + } +} diff --git a/PetaPoco/Core/GridReader.cs b/PetaPoco/Core/GridReader.cs index e78234b5..01fe8605 100644 --- a/PetaPoco/Core/GridReader.cs +++ b/PetaPoco/Core/GridReader.cs @@ -90,7 +90,8 @@ private IEnumerable SinglePocoFromIDataReader(int index) { while (index == _gridIndex) { - var factory = pd.GetFactory(_command.CommandText, _command.Connection.ConnectionString, 0, _reader.FieldCount, _reader, _defaultMapper) as Func; + var factory = pd.GetFactory(_command.CommandText, _command.Connection.ConnectionString, 0, _reader.FieldCount, + _reader, _defaultMapper, _db.Provider.UseOrdinaryIdentifiers) as Func; while (true) { @@ -147,7 +148,7 @@ private IEnumerable MultiPocoFromIDataReader(int index, Type[] types, obje var cmd = _command; var rdr = _reader; - var factory = MultiPocoFactory.GetFactory(types, cmd.Connection.ConnectionString, cmd.CommandText, rdr, _defaultMapper); + var factory = MultiPocoFactory.GetFactory(types, cmd.Connection.ConnectionString, cmd.CommandText, rdr, _defaultMapper, _db.Provider.UseOrdinaryIdentifiers); // if no projector function provided by caller, figure out the split points and connect them ourself if (transformer == null) diff --git a/PetaPoco/Core/IProvider.cs b/PetaPoco/Core/IProvider.cs index 4f724173..5db4d88d 100644 --- a/PetaPoco/Core/IProvider.cs +++ b/PetaPoco/Core/IProvider.cs @@ -18,6 +18,11 @@ public interface IProvider /// IPagingHelper PagingUtility { get; } + /// + /// Gets a flag indicating whether the DB uses ordinary identifier. + /// + bool UseOrdinaryIdentifiers { get; } + /// /// Gets a flag indicating whether the DB has native support for GUID/UUID. /// diff --git a/PetaPoco/Core/MultiPocoFactory.cs b/PetaPoco/Core/MultiPocoFactory.cs index 41208a48..572796ba 100644 --- a/PetaPoco/Core/MultiPocoFactory.cs +++ b/PetaPoco/Core/MultiPocoFactory.cs @@ -75,11 +75,11 @@ public static object GetAutoMapper(Type[] types) } // Find the split point in a result set for two different POCOs and return the POCO factory for the first - private static Delegate FindSplitPoint(Type typeThis, Type typeNext, string connectionString, string sql, IDataReader r, ref int pos, IMapper defaultMapper) + private static Delegate FindSplitPoint(Type typeThis, Type typeNext, string connectionString, string sql, IDataReader r, ref int pos, IMapper defaultMapper, bool ignoreCase) { // Last? if (typeNext == null) - return PocoData.ForType(typeThis, defaultMapper).GetFactory(sql, connectionString, pos, r.FieldCount - pos, r, defaultMapper); + return PocoData.ForType(typeThis, defaultMapper).GetFactory(sql, connectionString, pos, r.FieldCount - pos, r, defaultMapper, ignoreCase); // Get PocoData for the two types var pdThis = PocoData.ForType(typeThis, defaultMapper); @@ -94,7 +94,7 @@ private static Delegate FindSplitPoint(Type typeThis, Type typeNext, string conn var fieldName = r.GetName(pos); if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName))) { - return pdThis.GetFactory(sql, connectionString, firstColumn, pos - firstColumn, r, defaultMapper); + return pdThis.GetFactory(sql, connectionString, firstColumn, pos - firstColumn, r, defaultMapper, ignoreCase); } usedColumns.Add(fieldName, true); @@ -105,7 +105,7 @@ private static Delegate FindSplitPoint(Type typeThis, Type typeNext, string conn } // Create a multi-poco factory for a query - private static Func CreateMultiPocoFactory(Type[] types, string connectionString, string sql, IDataReader r, IMapper defaultMapper) + private static Func CreateMultiPocoFactory(Type[] types, string connectionString, string sql, IDataReader r, IMapper defaultMapper, bool ignoreCase) { var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory)); @@ -120,7 +120,7 @@ private static Delegate FindSplitPoint(Type typeThis, Type typeNext, string conn for (var i = 0; i < types.Length; i++) { // Add to list of delegates to call - var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, connectionString, sql, r, ref pos, defaultMapper); + var del = FindSplitPoint(types[i], i + 1 < types.Length ? types[i + 1] : null, connectionString, sql, r, ref pos, defaultMapper, ignoreCase); dels.Add(del); // Get the delegate @@ -149,11 +149,11 @@ internal static void FlushCaches() } // Get (or create) a multi-poco factory for a query - public static Func GetFactory(Type[] types, string connectionString, string sql, IDataReader r, IMapper defaultMapper) + public static Func GetFactory(Type[] types, string connectionString, string sql, IDataReader r, IMapper defaultMapper, bool ignoreCase) { var key = Tuple.Create(typeof(TRet), new ArrayKey(types), connectionString, sql, r.FieldCount); - return (Func) MultiPocoFactories.GetOrAdd(key, () => CreateMultiPocoFactory(types, connectionString, sql, r, defaultMapper)); + return (Func) MultiPocoFactories.GetOrAdd(key, () => CreateMultiPocoFactory(types, connectionString, sql, r, defaultMapper, ignoreCase)); } } } diff --git a/PetaPoco/Core/PocoData.cs b/PetaPoco/Core/PocoData.cs index 376f66a2..5e2c1f56 100644 --- a/PetaPoco/Core/PocoData.cs +++ b/PetaPoco/Core/PocoData.cs @@ -122,7 +122,7 @@ public PocoData(Type type, IMapper defaultMapper) /// Trying to use dynamic types with this method. public static PocoData ForType(Type type, IMapper defaultMapper) { - if (type == typeof(System.Dynamic.ExpandoObject)) + if (type == typeof(System.Dynamic.ExpandoObject) || type == typeof(ExpandoPoco)) throw new InvalidOperationException("Cannot use dynamic types with this method"); return _pocoDatas.GetOrAdd(type, () => new PocoData(type, defaultMapper)); @@ -138,7 +138,7 @@ public static PocoData ForType(Type type, IMapper defaultMapper) public static PocoData ForObject(object obj, string primaryKeyName, IMapper defaultMapper) { var t = obj.GetType(); - if (t == typeof(System.Dynamic.ExpandoObject)) + if (t == typeof(System.Dynamic.ExpandoObject) || t == typeof(ExpandoPoco)) { var pd = new PocoData(); pd.TableInfo = new TableInfo(); @@ -148,8 +148,16 @@ public static PocoData ForObject(object obj, string primaryKeyName, IMapper defa pd.TableInfo.AutoIncrement = true; foreach (var col in (obj as IDictionary).Keys) { - if (col != primaryKeyName) - pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); + if (col == primaryKeyName) continue; + if (pd.Columns.TryGetValue(col, out PocoColumn pc)) + { + //When col and primaryKeyName only differ by case, + //we need ColumnName to match case exactly. + pc.ColumnName = col; + continue; + } + + pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); } return pd; @@ -168,10 +176,13 @@ public static PocoData ForObject(object obj, string primaryKeyName, IMapper defa /// The number of columns in the record's database table. /// The data reader instance. /// The default mapper to use for the POCO. + /// Whether to make dynamic objects case insensitive.
+ /// When will return , otherwise .
+ /// Default is /// A delegate that can convert an record into a POCO. /// The POCO type is a value type, or the POCO type has no default constructor, or the /// POCO type is an interface or abstract class. - public Delegate GetFactory(string sql, string connectionString, int firstColumn, int columnCount, IDataReader reader, IMapper defaultMapper) + public Delegate GetFactory(string sql, string connectionString, int firstColumn, int columnCount, IDataReader reader, IMapper defaultMapper, bool ignoreCase = false) { // Create key for cache lookup var key = Tuple.Create(sql, connectionString, firstColumn, columnCount); @@ -186,8 +197,17 @@ public Delegate GetFactory(string sql, string connectionString, int firstColumn, if (Type == typeof(object)) { - // var poco = new T() - il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj + if (ignoreCase) + { + // var poco = new T(bool) + il.Emit(OpCodes.Ldc_I4_1); // true + il.Emit(OpCodes.Newobj, typeof(ExpandoPoco).GetConstructor(new Type[] { typeof(bool) })); // obj + } + else + { + // var poco = new T() + il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj + } MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); diff --git a/PetaPoco/Database.cs b/PetaPoco/Database.cs index f2a02776..d9248cbb 100644 --- a/PetaPoco/Database.cs +++ b/PetaPoco/Database.cs @@ -47,15 +47,16 @@ public class Database : IDatabase /// Constructs an instance with default values using the first connection string found in the app/web configuration file. /// /// The default mapper to use when no specific mapper has been registered. + /// Whether to use case insensitive dynamic objects. /// No connection strings are registered. - public Database(IMapper defaultMapper = null) + public Database(IMapper defaultMapper = null, bool ignoreCase = false) { if (ConfigurationManager.ConnectionStrings.Count == 0) throw new InvalidOperationException("One or more connection strings must be registered to use the no-parameter constructor"); var entry = ConfigurationManager.ConnectionStrings[0]; _connectionString = entry.ConnectionString; - InitialiseFromEntry(entry, defaultMapper); + InitialiseFromEntry(entry, defaultMapper, ignoreCase); } /// @@ -67,9 +68,10 @@ public Database(IMapper defaultMapper = null) /// /// The name of the connection string to locate. /// The default mapper to use when no specific mapper has been registered. + /// Whether to use case insensitive dynamic objects. /// is null or empty. /// A connection string cannot be found. - public Database(string connectionStringName, IMapper defaultMapper = null) + public Database(string connectionStringName, IMapper defaultMapper = null, bool ignoreCase = false) { if (string.IsNullOrEmpty(connectionStringName)) throw new ArgumentException("Connection string name must not be null or empty", nameof(connectionStringName)); @@ -80,13 +82,13 @@ public Database(string connectionStringName, IMapper defaultMapper = null) throw new InvalidOperationException(string.Format("Can't find a connection string with the name '{0}'", connectionStringName)); _connectionString = entry.ConnectionString; - InitialiseFromEntry(entry, defaultMapper); + InitialiseFromEntry(entry, defaultMapper, ignoreCase); } - private void InitialiseFromEntry(ConnectionStringSettings entry, IMapper defaultMapper) + private void InitialiseFromEntry(ConnectionStringSettings entry, IMapper defaultMapper, bool ignoreCase) { var providerName = !string.IsNullOrEmpty(entry.ProviderName) ? entry.ProviderName : "System.Data.SqlClient"; - Initialise(DatabaseProvider.Resolve(providerName, false, _connectionString), defaultMapper); + Initialise(DatabaseProvider.Resolve(providerName, false, _connectionString, ignoreCase), defaultMapper); } #endif @@ -98,14 +100,15 @@ private void InitialiseFromEntry(ConnectionStringSettings entry, IMapper default /// /// The database connection. /// The default mapper to use when no specific mapper has been registered. + /// Whether to use case insensitive dynamic objects. /// is null or empty. - public Database(IDbConnection connection, IMapper defaultMapper = null) + public Database(IDbConnection connection, IMapper defaultMapper = null, bool ignoreCase = false) { if (connection == null) throw new ArgumentNullException(nameof(connection)); SetupFromConnection(connection); - Initialise(DatabaseProvider.Resolve(_sharedConnection.GetType(), false, _connectionString), defaultMapper); + Initialise(DatabaseProvider.Resolve(_sharedConnection.GetType(), false, _connectionString, ignoreCase), defaultMapper); } /// @@ -117,9 +120,10 @@ public Database(IDbConnection connection, IMapper defaultMapper = null) /// The connection string. /// The database provider name. /// The default mapper to use when no specific mapper has been registered. + /// Whether to use case insensitive dynamic objects. /// or is null or /// empty. - public Database(string connectionString, string providerName, IMapper defaultMapper = null) + public Database(string connectionString, string providerName, IMapper defaultMapper = null, bool ignoreCase = false) { if (string.IsNullOrEmpty(connectionString)) throw new ArgumentException("Connection string must not be null or empty", nameof(connectionString)); @@ -127,7 +131,7 @@ public Database(string connectionString, string providerName, IMapper defaultMap throw new ArgumentException("Provider name must not be null or empty", nameof(providerName)); _connectionString = connectionString; - Initialise(DatabaseProvider.Resolve(providerName, false, _connectionString), defaultMapper); + Initialise(DatabaseProvider.Resolve(providerName, false, _connectionString, ignoreCase), defaultMapper); } /// @@ -139,9 +143,10 @@ public Database(string connectionString, string providerName, IMapper defaultMap /// The connection string. /// The database provider factory to use for database connections. /// The default mapper to use when no specific mapper has been registered. + /// Whether to use case insensitive dynamic objects. /// is null or empty. /// is null. - public Database(string connectionString, DbProviderFactory factory, IMapper defaultMapper = null) + public Database(string connectionString, DbProviderFactory factory, IMapper defaultMapper = null, bool ignoreCase = false) { if (string.IsNullOrEmpty(connectionString)) throw new ArgumentException("Connection string must not be null or empty", nameof(connectionString)); @@ -149,7 +154,7 @@ public Database(string connectionString, DbProviderFactory factory, IMapper defa throw new ArgumentNullException(nameof(factory)); _connectionString = connectionString; - Initialise(DatabaseProvider.Resolve(DatabaseProvider.Unwrap(factory).GetType(), false, _connectionString), defaultMapper); + Initialise(DatabaseProvider.Resolve(DatabaseProvider.Unwrap(factory).GetType(), false, _connectionString, ignoreCase), defaultMapper); } /// @@ -191,6 +196,9 @@ public Database(IDatabaseBuildConfiguration configuration) IMapper defaultMapper = null; settings.TryGetSetting(DatabaseConfigurationExtensions.DefaultMapper, v => defaultMapper = v); + var ignoreCase = false; + settings.TryGetSetting(DatabaseConfigurationExtensions.IgnoreCase, ic => ignoreCase = ic); + IProvider provider = null; IDbConnection connection = null; string providerName = null; @@ -241,13 +249,13 @@ public Database(IDatabaseBuildConfiguration configuration) if (provider != null) Initialise(provider, defaultMapper); else if (providerName != null) - Initialise(DatabaseProvider.Resolve(providerName, false, _connectionString), defaultMapper); + Initialise(DatabaseProvider.Resolve(providerName, false, _connectionString, ignoreCase), defaultMapper); #if !NETSTANDARD else if (entry != null) - InitialiseFromEntry(entry, defaultMapper); + InitialiseFromEntry(entry, defaultMapper, ignoreCase); #endif else if (connection != null) - Initialise(DatabaseProvider.Resolve(_sharedConnection.GetType(), false, _connectionString), defaultMapper); + Initialise(DatabaseProvider.Resolve(_sharedConnection.GetType(), false, _connectionString, ignoreCase), defaultMapper); else throw new InvalidOperationException("Unable to locate a provider."); @@ -1011,8 +1019,8 @@ protected virtual IEnumerable ExecuteReader(CommandType commandType, strin yield break; } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, r, - _defaultMapper) as Func; + var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, r.FieldCount, + r, _defaultMapper, Provider.UseOrdinaryIdentifiers) as Func; using (r) { while (true) @@ -1072,8 +1080,8 @@ protected virtual async Task> ExecuteReaderAsync(Cancellation return AsyncReader.Empty(); } - var factory = - pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, reader.FieldCount, reader, _defaultMapper) as Func; + var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, reader.FieldCount, + reader, _defaultMapper, Provider.UseOrdinaryIdentifiers) as Func; return new AsyncReader(this, cmd, reader, factory); } @@ -1101,9 +1109,8 @@ protected virtual async Task ExecuteReaderAsync(Action action, Cancellatio } var readerAsync = reader as DbDataReader; - var factory = - pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, reader.FieldCount, reader, - _defaultMapper) as Func; + var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, 0, reader.FieldCount, + reader, _defaultMapper, Provider.UseOrdinaryIdentifiers) as Func; using (reader) { @@ -1231,7 +1238,7 @@ public IEnumerable Query(Type[] types, object projector, strin yield break; } - var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r, _defaultMapper); + var factory = MultiPocoFactory.GetFactory(types, _sharedConnection.ConnectionString, sql, r, _defaultMapper, Provider.UseOrdinaryIdentifiers); if (projector == null) projector = MultiPocoFactory.GetAutoMapper(types.ToArray()); var bNeedTerminator = false; @@ -2757,7 +2764,7 @@ public bool IsNew(string primaryKeyName, object poco) /// name. protected virtual bool IsNew(string primaryKeyName, PocoData pocoData, object poco) { - if (string.IsNullOrEmpty(primaryKeyName) || poco is ExpandoObject) + if (string.IsNullOrEmpty(primaryKeyName) || poco is ExpandoObject || poco is ExpandoPoco) throw new InvalidOperationException("IsNew() and Save() are only supported on tables with identity (inc auto-increment) primary key columns"); object pk; @@ -3162,9 +3169,13 @@ private void AddParameter(IDbCommand cmd, object value, PocoColumn pc) idbParam.ParameterName = cmd.Parameters.Count.EnsureParamPrefix(_paramPrefix); else if (idbParam.ParameterName?.StartsWith(_paramPrefix) != true) { - // only add param prefix if it's not an Oracle stored procedures - if (!(cmd.CommandType == CommandType.StoredProcedure && _provider is Providers.OracleDatabaseProvider)) - idbParam.ParameterName = idbParam.ParameterName.EnsureParamPrefix(_paramPrefix); + var isOracleStoredProc = cmd.CommandType == CommandType.StoredProcedure && + _provider is Providers.OracleDatabaseProvider; + + // if it's an Oracle stored procedure, only escape the parameter name, don't add param prefix + idbParam.ParameterName = isOracleStoredProc ? + _provider.EscapeSqlIdentifier(idbParam.ParameterName) : + idbParam.ParameterName.EnsureParamPrefix(_paramPrefix); } cmd.Parameters.Add(idbParam); diff --git a/PetaPoco/DatabaseConfigurationExtensions.cs b/PetaPoco/DatabaseConfigurationExtensions.cs index ae25e88f..a8f44dd4 100644 --- a/PetaPoco/DatabaseConfigurationExtensions.cs +++ b/PetaPoco/DatabaseConfigurationExtensions.cs @@ -31,6 +31,7 @@ public static class DatabaseConfigurationExtensions internal const string ConnectionClosing = "ConnectionClosing"; internal const string ExceptionThrown = "ExceptionThrown"; internal const string Connection = "Connection"; + internal const string IgnoreCase = "IgnoreCase"; private static void SetSetting(this IDatabaseBuildConfiguration source, string key, object value) { @@ -268,6 +269,20 @@ public static IDatabaseBuildConfiguration UsingProvider(this IDatabas return source; } + /// + /// Whether to use case insensitive dynamic objects. + /// + /// + /// Only supported by Oracle providers. + /// + /// The configuration source. + /// The original configuration, to form a fluent interface. + public static IDatabaseBuildConfiguration UsingCaseInsensitiveProvider(this IDatabaseBuildConfiguration source) + { + source.SetSetting(IgnoreCase, true); + return source; + } + #endregion #region Mapper Settings diff --git a/PetaPoco/Providers/OracleDatabaseProvider.cs b/PetaPoco/Providers/OracleDatabaseProvider.cs index 7c16feac..c0efa4a0 100644 --- a/PetaPoco/Providers/OracleDatabaseProvider.cs +++ b/PetaPoco/Providers/OracleDatabaseProvider.cs @@ -35,6 +35,12 @@ public override void PreExecute(IDbCommand cmd) { cmd.GetType().GetProperty("BindByName")?.SetValue(cmd, true, null); cmd.GetType().GetProperty("InitialLONGFetchSize")?.SetValue(cmd, -1, null); + + //By default statements are cached, so if the database is modified between two reads (same statement), + //the last one will still access the old statement's definition. + //Setting this property to false prevents caching of statements completely. + //NOTE: Using "Statement Cache Purge=true; Statement Cache Size=0" in the ConnectionString is not a guarantee + cmd.GetType().GetProperty("AddToStatementCache")?.SetValue(cmd, false, null); } /// @@ -45,30 +51,31 @@ public override string BuildPageQuery(long skip, long take, SQLParts parts, ref throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); //Supported by Oracle v12c and above only - //var sql = $"{parts.Sql}\nOFFSET @{args.Length} ROWS FETCH NEXT @{args.Length + 1} ROWS ONLY"; - //args = args.Concat(new object[] { skip, take }).ToArray(); - //return sql; - - //Similar to SqlServerProvider with the exception of SELECT NULL FROM DUAL vs SELECT NULL - var helper = (PagingHelper)PagingUtility; - // when the query does not contain an "order by", it is very slow - if (helper.SimpleRegexOrderBy.IsMatch(parts.SqlSelectRemoved)) - { - var m = helper.SimpleRegexOrderBy.Match(parts.SqlSelectRemoved); - if (m.Success) - { - var g = m.Groups[0]; - parts.SqlSelectRemoved = parts.SqlSelectRemoved.Substring(0, g.Index); - } - } - - if (helper.RegexDistinct.IsMatch(parts.SqlSelectRemoved)) - parts.SqlSelectRemoved = "peta_inner.* FROM (SELECT " + parts.SqlSelectRemoved + ") peta_inner"; - - var sqlPage = - $"SELECT * FROM (SELECT ROW_NUMBER() OVER ({parts.SqlOrderBy ?? "ORDER BY (SELECT NULL FROM DUAL)"}) peta_rn, {parts.SqlSelectRemoved}) peta_paged WHERE peta_rn > @{args.Length} AND peta_rn <= @{args.Length + 1}"; - args = args.Concat(new object[] { skip, skip + take }).ToArray(); - return sqlPage; + var sql = $"{parts.Sql}\nOFFSET @{args.Length} ROWS FETCH NEXT @{args.Length + 1} ROWS ONLY"; + args = args.Concat(new object[] { skip, take }).ToArray(); + return sql; + + //Older versions of Oracle + ////Similar to SqlServerProvider with the exception of SELECT NULL FROM DUAL vs SELECT NULL + //var helper = (PagingHelper)PagingUtility; + //// when the query does not contain an "order by", it is very slow + //if (helper.SimpleRegexOrderBy.IsMatch(parts.SqlSelectRemoved)) + //{ + // var m = helper.SimpleRegexOrderBy.Match(parts.SqlSelectRemoved); + // if (m.Success) + // { + // var g = m.Groups[0]; + // parts.SqlSelectRemoved = parts.SqlSelectRemoved.Substring(0, g.Index); + // } + //} + + //if (helper.RegexDistinct.IsMatch(parts.SqlSelectRemoved)) + // parts.SqlSelectRemoved = "peta_inner.* FROM (SELECT " + parts.SqlSelectRemoved + ") peta_inner"; + + //var sqlPage = + // $"SELECT * FROM (SELECT ROW_NUMBER() OVER ({parts.SqlOrderBy ?? "ORDER BY (SELECT NULL FROM DUAL)"}) peta_rn, {parts.SqlSelectRemoved}) peta_paged WHERE peta_rn > @{args.Length} AND peta_rn <= @{args.Length + 1}"; + //args = args.Concat(new object[] { skip, skip + take }).ToArray(); + //return sqlPage; } /// @@ -83,18 +90,13 @@ public override DbProviderFactory GetFactory() /// public override string EscapeSqlIdentifier(string sqlIdentifier) { - return sqlIdentifier; - - //TODO: Below code determines whether it is required to wrap the identifier in double quotes or not. - // Included for convenience while fixing failing tests until we're sure it's not required. - //If already quoted, leave as-is if (_delimitedIdentifierRegex.IsMatch(sqlIdentifier)) return sqlIdentifier; - //If no quotes required, leave as-is (could also uppercase) - if (_ordinaryIdentifierRegex.IsMatch(sqlIdentifier)) return sqlIdentifier; //.ToUpperInvariant(); + //If using ordinary identifiers and no quotes required, leave as-is (could also uppercase) + if (UseOrdinaryIdentifiers && _ordinaryIdentifierRegex.IsMatch(sqlIdentifier)) return sqlIdentifier; //.ToUpperInvariant(); - //If not valid, wrap in quotes, but don't allow use of double quotes in identifier + //If using delimited identifiers or quotes required, wrap in quotes, but don't allow use of double quotes in identifier return "\"" + sqlIdentifier.Replace("\"", "").Replace(".", "\".\"") + "\""; } diff --git a/PetaPoco/Providers/OracleOrdinaryDatabaseProvider.cs b/PetaPoco/Providers/OracleOrdinaryDatabaseProvider.cs new file mode 100644 index 00000000..e12c3268 --- /dev/null +++ b/PetaPoco/Providers/OracleOrdinaryDatabaseProvider.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PetaPoco.Providers +{ + /// + /// + /// + /// + ///
+ /// It utilizes the builtin for dynamic objects to accomodate the use of ordinary identifiers.
+ /// For delimited identifiers, use + ///
+ public class OracleOrdinaryDatabaseProvider : OracleDatabaseProvider + { + /// + public override bool UseOrdinaryIdentifiers => true; + } +} diff --git a/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabase.sql b/Scripts/Startup/oracle.sql similarity index 70% rename from PetaPoco.Tests.Integration/Scripts/OracleSetupDatabase.sql rename to Scripts/Startup/oracle.sql index 05f1c0f8..5e3c9f32 100644 --- a/PetaPoco.Tests.Integration/Scripts/OracleSetupDatabase.sql +++ b/Scripts/Startup/oracle.sql @@ -1,22 +1,4 @@ --- Drop PETAPOCO user COMPLETELY (if it exists) -DECLARE - found number := 0; -BEGIN - SELECT COUNT(*) INTO found - FROM all_users - WHERE username = 'PETAPOCO'; - - IF found <> 0 THEN - BEGIN - EXECUTE IMMEDIATE 'DROP USER petapoco CASCADE'; - END; - END IF; -END; -/ - --- Create fresh user -CREATE USER petapoco IDENTIFIED BY petapoco; -/ +ALTER SESSION SET container=FREEPDB1; -- Drop DATA_TS tablespace COMPLETELY (if it exists) DECLARE @@ -74,13 +56,4 @@ CREATE ROLE app_dev_role; -- Grant privileges to the application developer role GRANT CREATE SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE PROCEDURE, CREATE TRIGGER, CREATE VIEW TO app_dev_role; GRANT EXECUTE ON dbms_lock TO app_dev_role; -GRANT EXECUTE ON DROP_IF_EXISTS TO app_dev_role; - --- Ensure that the tablespace created above, is the default for the user. This tablespace will be used when creating tables for example -ALTER USER petapoco DEFAULT TABLESPACE data_ts; --- Give user quota e.g. to perform inserts -ALTER USER petapoco QUOTA UNLIMITED ON data_ts; - --- Grant the application developer role -GRANT app_dev_role TO petapoco; -/ +GRANT EXECUTE ON DROP_IF_EXISTS TO app_dev_role; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 731aa384..ffd51485 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,5 +84,7 @@ services: - "5008:1521" environment: <<: *oracle-environment + volumes: + - ./Scripts/Startup:/opt/oracle/scripts/startup # TODO: teradata