diff --git a/src/EFCore.Jet.Data/JetConnection.cs b/src/EFCore.Jet.Data/JetConnection.cs index 8b8a1b84..e83ac5f2 100644 --- a/src/EFCore.Jet.Data/JetConnection.cs +++ b/src/EFCore.Jet.Data/JetConnection.cs @@ -210,9 +210,7 @@ public override int ConnectionTimeout /// protected override DbCommand CreateDbCommand() { - var command = JetFactory.CreateCommand(); - command.Connection = this; - return command; + return CreateCommand(null); } /// diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs index 905958be..10f27719 100644 --- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs +++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs @@ -53,8 +53,10 @@ static JetStoreDatabaseHandling() RegexOptions.IgnoreCase); // Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb; + // Provider=Microsoft.ACE.OLEDB.12.0;Data Source="Joe's Database.accdb"; + // Provider=Microsoft.ACE.OLEDB.12.0;Data Source='Joe''s Database.accdb'; _regExExtractFilenameFromConnectionString = new Regex( - @"^(?:.*;)?\s*(?:data source|dbq)\s*=\s*(?.*?)\s*(?:;|$)", + @"^(?:.*;)?\s*(?:data source|dbq)\s*=\s*(?:(?[""'])\s*(?.*?)\s*\k|(?.*?))\s*(?:;|$)", RegexOptions.IgnoreCase); // CREATE DATABASE Joe's Database.accdb; @@ -171,14 +173,21 @@ public static bool ProcessDatabaseOperation(JetCommand command) public static string ExtractFileNameFromConnectionString(string connectionString) { - string fileName; - Match match = _regExExtractFilenameFromConnectionString.Match(connectionString); + var match = _regExExtractFilenameFromConnectionString.Match(connectionString); if (match.Success) - fileName = match.Groups["filename"] - .Value; - else - fileName = connectionString; - return fileName; + { + var fileName = match.Groups["filename"].Value; + + if (match.Groups["quote"].Success) + { + var quoteChar = match.Groups["quote"].Value; + fileName = fileName.Replace(quoteChar + quoteChar, quoteChar); + } + + return fileName; + } + + return connectionString; } public static bool IsConnectionString(string connectionString) diff --git a/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs b/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs index 47c3379b..20169792 100644 --- a/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs +++ b/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using EntityFrameworkCore.Jet.Data; using EntityFrameworkCore.Jet.Internal; using EntityFrameworkCore.Jet.Migrations.Operations; using JetBrains.Annotations; @@ -101,7 +102,17 @@ private IRelationalCommand CreateHasTablesCommand() => _rawSqlCommandBuilder.Build(@"SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE IN ('BASE TABLE', 'VIEW')"); private IReadOnlyList CreateCreateOperations() - => Dependencies.MigrationsSqlGenerator.Generate(new[] {new JetCreateDatabaseOperation {Name = _relationalConnection.DbConnection.DataSource}}); + { + var dataSource = _relationalConnection.DbConnection.DataSource; + + // Alternative: + // var connection = (JetConnection) _relationalConnection.DbConnection; + // var csb = connection.JetFactory.CreateConnectionStringBuilder(); + // csb.ConnectionString = connection.ConnectionString; + // var dataSource = csb.GetDataSource(); + + return Dependencies.MigrationsSqlGenerator.Generate(new[] {new JetCreateDatabaseOperation {Name = dataSource}}); + } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/test/EFCore.Jet.Data.Tests/ConnectionStringTest.cs b/test/EFCore.Jet.Data.Tests/ConnectionStringTest.cs new file mode 100644 index 00000000..6bc0e7f5 --- /dev/null +++ b/test/EFCore.Jet.Data.Tests/ConnectionStringTest.cs @@ -0,0 +1,47 @@ +using System.Data.Odbc; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EntityFrameworkCore.Jet.Data.Tests +{ + [TestClass] + public class ConnectionStringTest + { + [TestMethod] + public void Escape_double_quoted_connection_string() + { + var expectedDatabaseName = "Joe's \"Recipes\" Database.accdb"; + var escapedDatabaseName = expectedDatabaseName.Replace("\"", "\"\""); + + var connectionString = Helpers.DataAccessProviderFactory is OdbcFactory + ? $"DBQ=\"{escapedDatabaseName}\"" + : $"Data Source=\"{escapedDatabaseName}\""; + + var csb = Helpers.DataAccessProviderFactory.CreateConnectionStringBuilder(); + csb.ConnectionString = connectionString; + + var actualDatabaseName = csb.GetDataSource(); + + Assert.AreEqual(expectedDatabaseName, actualDatabaseName); + } + + [TestMethod] + public void Escape_single_quoted_connection_string() + { + var expectedDatabaseName = "Joe's \"Recipes\" Database.accdb"; + var escapedDatabaseName = expectedDatabaseName.Replace("'", "''"); + + var connectionString = Helpers.DataAccessProviderFactory is OdbcFactory + ? $"DBQ='{escapedDatabaseName}'" + : $"Data Source='{escapedDatabaseName}'"; + + using var connection = new JetConnection(connectionString, Helpers.DataAccessProviderFactory); + + var csb = Helpers.DataAccessProviderFactory.CreateConnectionStringBuilder(); + csb.ConnectionString = connectionString; + + var actualDatabaseName = csb.GetDataSource(); + + Assert.AreEqual(expectedDatabaseName, actualDatabaseName); + } + } +} \ No newline at end of file diff --git a/test/EFCore.Jet.Data.Tests/CreateDatabaseTest.cs b/test/EFCore.Jet.Data.Tests/CreateDatabaseTest.cs index 3c0bc57f..00e6c092 100644 --- a/test/EFCore.Jet.Data.Tests/CreateDatabaseTest.cs +++ b/test/EFCore.Jet.Data.Tests/CreateDatabaseTest.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Data.Odbc; using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -25,31 +26,67 @@ public void TearDown() public void CreateAndDropDatabaseFromConnection() { using var connection = new JetConnection(StoreName, Helpers.DataAccessProviderFactory); + connection.CreateDatabase(); - Assert.IsTrue(File.Exists(StoreName)); - - using var command = connection.CreateCommand(); - command.CommandText = "DROP DATABASE " + StoreName; - command.ExecuteNonQuery(); - + + connection.DropDatabase(); Assert.IsFalse(File.Exists(StoreName)); } [TestMethod] public void CreateAndDropDatabaseWithUnsetConnection() { - using var connection = new JetConnection(); + using var connection = new JetConnection(Helpers.DataAccessProviderFactory); var command = connection.CreateCommand(); - command.CommandText = "CREATE DATABASE " + StoreName; + command.CommandText = $"CREATE DATABASE '{StoreName}'"; command.ExecuteNonQuery(); - Assert.IsTrue(File.Exists(StoreName)); - command.CommandText = "DROP DATABASE " + StoreName; + command.CommandText = $"DROP DATABASE '{StoreName}'"; command.ExecuteNonQuery(); + Assert.IsFalse(File.Exists(StoreName)); + } + + [TestMethod] + public void CreateAndDropDatabaseWithUnsetConnectionWithoutDataAccessProviderFactoryThrows() + { + using var connection = new JetConnection(); + + Assert.ThrowsException( + () => { using var command = connection.CreateCommand(); }); + } + + [TestMethod] + public void CreateAndDropDatabaseWithSingleQuotedConnectionString() + { + var connectionString = Helpers.DataAccessProviderFactory is OdbcFactory + ? $"DBQ='{StoreName}'" + : $"Data Source='{StoreName}'"; + + using var connection = new JetConnection(connectionString, Helpers.DataAccessProviderFactory); + + connection.CreateDatabase(); + Assert.IsTrue(File.Exists(StoreName)); + + connection.DropDatabase(); + Assert.IsFalse(File.Exists(StoreName)); + } + + [TestMethod] + public void CreateAndDropDatabaseWithDoubleQuotedConnectionString() + { + var connectionString = Helpers.DataAccessProviderFactory is OdbcFactory + ? $"DBQ=\"{StoreName}\"" + : $"Data Source=\"{StoreName}\""; + using var connection = new JetConnection(connectionString, Helpers.DataAccessProviderFactory); + + connection.CreateDatabase(); + Assert.IsTrue(File.Exists(StoreName)); + + connection.DropDatabase(); Assert.IsFalse(File.Exists(StoreName)); } } diff --git a/test/EFCore.Jet.Tests/TestBase.cs b/test/EFCore.Jet.Tests/TestBase.cs index 901b565b..78b5f9d8 100644 --- a/test/EFCore.Jet.Tests/TestBase.cs +++ b/test/EFCore.Jet.Tests/TestBase.cs @@ -11,11 +11,12 @@ public class TestBase : IDisposable { public TestBase() { - TestStore = JetTestStore.CreateInitialized(nameof(JetMigrationTest)); + TestStore = JetTestStore.CreateInitialized(StoreName); } public virtual void Dispose() => TestStore.Dispose(); + public virtual string StoreName => GetType().Name; public virtual JetTestStore TestStore { get; } public virtual List SqlCommands { get; } = new List(); public virtual string Sql => string.Join("\n\n", SqlCommands);