From 9b8ea396767f80ca4a30ce9086dcdf3f2655872a Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 12 Nov 2015 11:57:13 -0800 Subject: [PATCH] Remove SqliteDmlParser --- .../EntityFramework.Sqlite.Design.csproj | 5 +- .../Internal/SqliteDmlParser.cs | 221 ------------------ .../SqliteDatabaseModelFactory.cs | 66 +++--- ...ework.Sqlite.Design.FunctionalTests.csproj | 4 +- ...mework.Sqlite.Design.FunctionalTests.xproj | 4 +- .../SqliteAllFluentApiE2ETest.cs | 2 +- ...liteAttributesInsteadOfFluentApiE2ETest.cs | 2 +- .../ReverseEngineering/SqliteE2ETestBase.cs | 4 +- .../SqliteDatabaseModelFactoryTest.cs | 7 +- ...EntityFramework.Sqlite.Design.Tests.csproj | 5 +- .../SqliteDmlParserTest.cs | 58 ----- 11 files changed, 49 insertions(+), 329 deletions(-) delete mode 100644 src/EntityFramework.Sqlite.Design/Internal/SqliteDmlParser.cs delete mode 100644 test/EntityFramework.Sqlite.Design.Tests/SqliteDmlParserTest.cs diff --git a/src/EntityFramework.Sqlite.Design/EntityFramework.Sqlite.Design.csproj b/src/EntityFramework.Sqlite.Design/EntityFramework.Sqlite.Design.csproj index 35465528537..6950fb69e6e 100644 --- a/src/EntityFramework.Sqlite.Design/EntityFramework.Sqlite.Design.csproj +++ b/src/EntityFramework.Sqlite.Design/EntityFramework.Sqlite.Design.csproj @@ -52,7 +52,6 @@ - @@ -78,11 +77,11 @@ - - \ No newline at end of file + diff --git a/src/EntityFramework.Sqlite.Design/Internal/SqliteDmlParser.cs b/src/EntityFramework.Sqlite.Design/Internal/SqliteDmlParser.cs deleted file mode 100644 index ac27c49ed04..00000000000 --- a/src/EntityFramework.Sqlite.Design/Internal/SqliteDmlParser.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Data.Entity.Scaffolding.Metadata; - -namespace Microsoft.Data.Entity.Scaffolding.Internal -{ - internal static class SqliteDmlParser - { - private static readonly ISet _constraintKeyWords = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "CONSTRAINT", - "PRIMARY", - "UNIQUE", - "CHECK", - "FOREIGN" - }; - - public static void ParseTableDefinition(TableModel table, string sql) - { - var statements = ParseStatements(sql).ToList(); - var i = 0; - for (; i < statements.Count; i++) - { - var firstWord = statements[i].Split(' ', '(')[0]; - if (_constraintKeyWords.Contains(firstWord)) - { - break; // once we see the first constraint, stop looking for params - } - ParseColumnDefinition(table, statements[i]); - } - for (; i < statements.Count; i++) - { - ParseConstraints(table, statements[i]); - } - } - - // Extract all column definitions and constraints - // Splits on commas, accounting for commas with parenthesis and quotes - public static IEnumerable ParseStatements(string sql) - { - char c; - var start = 0; - while ((c = sql[start++]) != '(') - { - if (c == '"') - { - while (sql[start++] != '"') - { - ; - } - } - if (c == '\'') - { - while (sql[start++] != '\'') - { - ; - } - } - } - var statementsChunk = sql.Substring(start, sql.LastIndexOf(')') - start); - - return SafeSplit(statementsChunk, ',').Select(s => s.Trim()); - } - - public static void ParseColumnDefinition(TableModel table, string statement) - { - var paramName = UnescapeString(SafeSplit(statement, ' ').First()); - var column = table.Columns.FirstOrDefault(c => c.Name.Equals(paramName, StringComparison.OrdinalIgnoreCase)); - if (column == null) - { - return; - } - - if (statement.IndexOf(" UNIQUE", StringComparison.OrdinalIgnoreCase) > 0) - { - var indexInfo = table.Indexes.FirstOrDefault(i => - i.Columns.SingleOrDefault()?.Name.Equals(column.Name, StringComparison.OrdinalIgnoreCase) == true); - - if (indexInfo != null) - { - indexInfo.IsUnique = true; - } - } - } - - public static void ParseConstraints(TableModel table, string statement) - { - var constraint = statement.Split(' ', '(')[0]; - if (constraint.Equals("UNIQUE", StringComparison.OrdinalIgnoreCase)) - { - ParseInlineUniqueConstraint(table, statement); - } - } - - public static void ParseInlineUniqueConstraint(TableModel table, string statement) - { - var start = statement.IndexOf('(') + 1; - var paramChunk = statement.Substring(start, statement.LastIndexOf(')') - start); - var columns = SafeSplit(paramChunk, ',') - .Select(UnescapeString) - .ToList(); - - var index = table.Indexes.FirstOrDefault(i => - { - if (!i.Name.StartsWith("sqlite_autoindex") - || !i.Table.Name.Equals(table.Name, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - return columns.All(prop => i.Columns.Any(p => p.Name.Equals(prop, StringComparison.OrdinalIgnoreCase))); - }); - - if (index != null) - { - index.IsUnique = true; - } - } - - public static string UnescapeString(string identifier) - { - identifier = identifier.Trim(); - var firstChar = identifier[0]; - char quote; - if (firstChar != identifier[identifier.Length - 1]) - { - return identifier; - } - - if (firstChar == '"') - { - quote = '"'; - } - else if (firstChar == '\'') - { - quote = '\''; - } - else - { - return identifier; - } - - return identifier.Substring(1, identifier.Length - 2).Replace($"{quote}{quote}", quote.ToString()); - } - - // For internal use. Only designed to split a valid SQLite statements. - internal static IEnumerable SafeSplit(string str, char separator) - { - // ReSharper disable EmptyEmbeddedStatement - var idx = -1; - var lastStart = 0; - char c; - while (++idx < str.Length) - { - c = str[idx]; - if (c == separator) - { - if (idx > lastStart) - { - yield return str.Substring(lastStart, idx - lastStart).Trim(separator); - } - lastStart = idx + 1; - } - else if (c == '(') - { - var nestedLevel = 1; - while (nestedLevel > 0 - && ++idx < str.Length) - { - switch (str[idx]) - { - case ')': - nestedLevel--; - break; - case '(': - nestedLevel++; - break; - case '"': - while (++idx < str.Length - && str[idx] != '"') - { - ; - } - break; - case '\'': - while (++idx < str.Length - && str[idx] != '\'') - { - ; - } - break; - } - } - } - else if (c == '"') - { - while (++idx < str.Length - && str[idx] != '"') - { - ; - } - } - else if (c == '\'') - { - while (++idx < str.Length - && str[idx] != '\'') - { - ; - } - } - } - if (idx > lastStart) - { - yield return str.Substring(lastStart, idx - lastStart).Trim(separator); - } - } - } -} diff --git a/src/EntityFramework.Sqlite.Design/SqliteDatabaseModelFactory.cs b/src/EntityFramework.Sqlite.Design/SqliteDatabaseModelFactory.cs index 70a2f13c484..0792d21437a 100644 --- a/src/EntityFramework.Sqlite.Design/SqliteDatabaseModelFactory.cs +++ b/src/EntityFramework.Sqlite.Design/SqliteDatabaseModelFactory.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using JetBrains.Annotations; using Microsoft.Data.Entity.Migrations; -using Microsoft.Data.Entity.Scaffolding.Internal; using Microsoft.Data.Entity.Scaffolding.Metadata; using Microsoft.Data.Entity.Utilities; using Microsoft.Data.Sqlite; @@ -19,8 +19,6 @@ public class SqliteDatabaseModelFactory : IDatabaseModelFactory private TableSelectionSet _tableSelectionSet; private DatabaseModel _databaseModel; private Dictionary _tables; - private Dictionary _indexDefinitions; - private Dictionary _tableDefinitions; private Dictionary _tableColumns; private static string ColumnKey(TableModel table, string columnName) => "[" + table.Name + "].[" + columnName + "]"; @@ -32,8 +30,6 @@ private void ResetState() _databaseModel = new DatabaseModel(); _tables = new Dictionary(StringComparer.OrdinalIgnoreCase); _tableColumns = new Dictionary(StringComparer.OrdinalIgnoreCase); - _tableDefinitions = new Dictionary(StringComparer.OrdinalIgnoreCase); - _indexDefinitions = new Dictionary(StringComparer.OrdinalIgnoreCase); } public virtual DatabaseModel Create( @@ -64,12 +60,6 @@ public virtual DatabaseModel Create( GetSqliteMaster(); GetColumns(); GetIndexes(); - - foreach (var table in _databaseModel.Tables) - { - SqliteDmlParser.ParseTableDefinition(table, _tableDefinitions[table.Name]); - } - GetForeignKeys(); return _databaseModel; } @@ -78,15 +68,14 @@ public virtual DatabaseModel Create( private void GetSqliteMaster() { var command = _connection.CreateCommand(); - command.CommandText = "SELECT type, name, sql, tbl_name FROM sqlite_master ORDER BY type DESC"; + command.CommandText = "SELECT type, name, tbl_name FROM sqlite_master ORDER BY type DESC"; using (var reader = command.ExecuteReader()) { while (reader.Read()) { var type = reader.GetString(0); var name = reader.GetString(1); - var sql = reader.GetValue(2) as string; // can be null - var tableName = reader.GetString(3); + var tableName = reader.GetString(2); if (type == "table" && name != "sqlite_sequence" @@ -98,7 +87,6 @@ private void GetSqliteMaster() }; _databaseModel.Tables.Add(table); _tables.Add(name, table); - _tableDefinitions[name] = sql; } else if (type == "index" && _tables.ContainsKey(tableName)) @@ -110,8 +98,6 @@ private void GetSqliteMaster() Name = name, Table = table }); - - _indexDefinitions[name] = sql; } } } @@ -161,6 +147,15 @@ private void GetColumns() } } + private enum IndexListColumns + { + Seqno, + Name, + Unique, + Origin, + Partial + } + private enum IndexInfoColumns { Seqno, @@ -172,13 +167,30 @@ private void GetIndexes() { foreach (var table in _databaseModel.Tables) { + var indexInfo = _connection.CreateCommand(); + indexInfo.CommandText = $"PRAGMA index_list(\"{table.Name.Replace("\"", "\"\"")}\");"; + + using (var reader = indexInfo.ExecuteReader()) + { + while (reader.Read()) + { + var indexName = reader.GetValue((int)IndexListColumns.Name) as string; + var isUnique = reader.GetBoolean((int)IndexListColumns.Unique); + var index = table.Indexes.FirstOrDefault(i => i.Name.Equals(indexName, StringComparison.OrdinalIgnoreCase)); + if (index != null) + { + index.IsUnique = isUnique; + } + } + } + foreach (var index in table.Indexes) { - var indexInfo = _connection.CreateCommand(); - indexInfo.CommandText = $"PRAGMA index_info(\"{index.Name.Replace("\"", "\"\"")}\");"; + var indexColumns = _connection.CreateCommand(); + indexColumns.CommandText = $"PRAGMA index_info(\"{index.Name.Replace("\"", "\"\"")}\");"; index.Columns = new List(); - using (var reader = indexInfo.ExecuteReader()) + using (var reader = indexColumns.ExecuteReader()) { while (reader.Read()) { @@ -191,16 +203,6 @@ private void GetIndexes() var column = _tableColumns[ColumnKey(table, columnName)]; index.Columns.Add(column); - - var sql = _indexDefinitions[index.Name]; - - if (!string.IsNullOrEmpty(sql)) - { - var uniqueKeyword = sql.IndexOf("UNIQUE", StringComparison.OrdinalIgnoreCase); - var indexKeyword = sql.IndexOf("INDEX", StringComparison.OrdinalIgnoreCase); - - index.IsUnique = uniqueKeyword > 0 && uniqueKeyword < indexKeyword; - } } } } @@ -234,7 +236,7 @@ private void GetForeignKeys() { var id = reader.GetInt32((int)ForeignKeyList.Id); var principalTableName = reader.GetString((int)ForeignKeyList.Table); - + ForeignKeyModel foreignKey; if (!tableForeignKeys.TryGetValue(id, out foreignKey)) { @@ -255,7 +257,7 @@ private void GetForeignKeys() { var toColumnName = reader.GetString((int)ForeignKeyList.To); ColumnModel toColumn; - if(!_tableColumns.TryGetValue(ColumnKey(foreignKey.PrincipalTable, toColumnName), out toColumn)) + if (!_tableColumns.TryGetValue(ColumnKey(foreignKey.PrincipalTable, toColumnName), out toColumn)) { toColumn = new ColumnModel { Name = toColumnName }; } diff --git a/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.csproj b/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.csproj index 723f8875d0d..4b7fd5857fb 100644 --- a/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.csproj +++ b/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.csproj @@ -8,7 +8,7 @@ {2AF716DA-9AFA-43B6-9DEC-B419A59B39FD} Library Properties - EntityFramework.Sqlite.Design.FunctionalTests + Microsoft.Data.Entity.Sqlite.Design.FunctionalTests EntityFramework.Sqlite.Design.FunctionalTests v4.6 512 @@ -191,4 +191,4 @@ - \ No newline at end of file + diff --git a/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.xproj b/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.xproj index 7cd0d603923..17a3c5546e6 100644 --- a/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.xproj +++ b/test/EntityFramework.Sqlite.Design.FunctionalTests/EntityFramework.Sqlite.Design.FunctionalTests.xproj @@ -7,7 +7,7 @@ d4a29871-788d-4ea3-b637-1f8d14da23b4 - EntityFramework.Sqlite.Design.FunctionalTests + Microsoft.Data.Entity.Sqlite.Design.FunctionalTests ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\bin\$(MSBuildProjectName)\ @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAllFluentApiE2ETest.cs b/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAllFluentApiE2ETest.cs index 717faf0954e..a2887fc15b9 100644 --- a/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAllFluentApiE2ETest.cs +++ b/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAllFluentApiE2ETest.cs @@ -4,7 +4,7 @@ using System.IO; using Xunit.Abstractions; -namespace EntityFramework.Sqlite.Design.FunctionalTests.ReverseEngineering +namespace Microsoft.Data.Entity.Sqlite.Design.FunctionalTests.ReverseEngineering { public class SqliteAllFluentApiE2ETest : SqliteE2ETestBase { diff --git a/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAttributesInsteadOfFluentApiE2ETest.cs b/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAttributesInsteadOfFluentApiE2ETest.cs index af8023d5750..9791c2b9761 100644 --- a/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAttributesInsteadOfFluentApiE2ETest.cs +++ b/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteAttributesInsteadOfFluentApiE2ETest.cs @@ -4,7 +4,7 @@ using System.IO; using Xunit.Abstractions; -namespace EntityFramework.Sqlite.Design.FunctionalTests.ReverseEngineering +namespace Microsoft.Data.Entity.Sqlite.Design.FunctionalTests.ReverseEngineering { public class SqliteAttributesInsteadOfFluentApiE2ETest : SqliteE2ETestBase { diff --git a/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteE2ETestBase.cs b/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteE2ETestBase.cs index c4eea24cbee..74c38ff092c 100644 --- a/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteE2ETestBase.cs +++ b/test/EntityFramework.Sqlite.Design.FunctionalTests/ReverseEngineering/SqliteE2ETestBase.cs @@ -14,7 +14,7 @@ using Xunit; using Xunit.Abstractions; -namespace EntityFramework.Sqlite.Design.FunctionalTests.ReverseEngineering +namespace Microsoft.Data.Entity.Sqlite.Design.FunctionalTests.ReverseEngineering { public abstract class SqliteE2ETestBase : E2ETestBase { @@ -421,7 +421,7 @@ FOREIGN KEY (UserAltId) REFERENCES User (AltId) protected abstract string ExpectedResultsParentDir { get; } protected abstract bool UseFluentApiOnly { get; } - protected override void ConfigureDesignTimeServices(IServiceCollection services) + protected override void ConfigureDesignTimeServices(IServiceCollection services) => new SqliteDesignTimeServices().ConfigureDesignTimeServices(services); } } diff --git a/test/EntityFramework.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs b/test/EntityFramework.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs index 61aea34b09b..ebc08dedbaf 100644 --- a/test/EntityFramework.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs +++ b/test/EntityFramework.Sqlite.Design.FunctionalTests/SqliteDatabaseModelFactoryTest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Microsoft.Data.Entity; using Microsoft.Data.Entity.FunctionalTests; using Microsoft.Data.Entity.Internal; using Microsoft.Data.Entity.Metadata; @@ -17,7 +16,7 @@ using Microsoft.Extensions.Logging; using Xunit; -namespace EntityFramework.Sqlite.Design.FunctionalTests +namespace Microsoft.Data.Entity.Sqlite.Design.FunctionalTests { public class SqliteDatabaseModelFactoryTest { @@ -228,8 +227,8 @@ FOREIGN KEY (ParentId) REFERENCES Parent (Id) GetModel(sql); - Assert.Contains("Warning: " + - RelationalDesignStrings.ForeignKeyScaffoldErrorPropertyNotFound("Children(ParentId)"), + Assert.Contains("Warning: " + + RelationalDesignStrings.ForeignKeyScaffoldErrorPropertyNotFound("Children(ParentId)"), _logger.FullLog); } diff --git a/test/EntityFramework.Sqlite.Design.Tests/EntityFramework.Sqlite.Design.Tests.csproj b/test/EntityFramework.Sqlite.Design.Tests/EntityFramework.Sqlite.Design.Tests.csproj index 76fab317884..7621b8778a0 100644 --- a/test/EntityFramework.Sqlite.Design.Tests/EntityFramework.Sqlite.Design.Tests.csproj +++ b/test/EntityFramework.Sqlite.Design.Tests/EntityFramework.Sqlite.Design.Tests.csproj @@ -42,7 +42,6 @@ - @@ -67,11 +66,11 @@ - - \ No newline at end of file + diff --git a/test/EntityFramework.Sqlite.Design.Tests/SqliteDmlParserTest.cs b/test/EntityFramework.Sqlite.Design.Tests/SqliteDmlParserTest.cs deleted file mode 100644 index cad9624e9d9..00000000000 --- a/test/EntityFramework.Sqlite.Design.Tests/SqliteDmlParserTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Linq; -using Microsoft.Data.Entity.Scaffolding.Internal; -using Xunit; - -namespace Microsoft.Data.Entity.Sqlite.Design -{ - public class SqliteDmlParserTest - { - public static object[][] Statements = - { - new object[] { "CREATE TABLE t (a,b,d);", new[] { "a", "b", "d" } }, - new object[] { "CREATE TABLE t ( \"col,name\" text, col2);", new[] { "\"col,name\" text", "col2" } }, - new object[] { "CREATE TABLE t ( \", \"\"col,name\" text, col2);", new[] { "\", \"\"col,name\" text", "col2" } }, - new object[] { "CREATE TABLE t (Int decimal(10,3), col2);", new[] { "Int decimal(10,3)", "col2" } }, - new object[] { "CREATE TABLE t (a string, foreign key (a) references b (\"a)\") );", new[] { "a string", "foreign key (a) references b (\"a)\")" } }, - new object[] - { - "CREATE TABLE t ( Int decimal(10,3) default (IF(1 + (3) = 3, 10.2, NULL)) , col2);", - new[] { "Int decimal(10,3) default (IF(1 + (3) = 3, 10.2, NULL))", "col2" } - }, - new object[] { "CREATE TABLE '(' ( A,B)", new object[] { "A", "B" } } - }; - - [Theory] - [MemberData(nameof(Statements))] - public void It_extracts_statements(string sql, string[] statements) - { - Assert.Equal(statements, SqliteDmlParser.ParseStatements(sql).ToArray()); - } - - [Theory] - [InlineData("'Escaped''single'", "Escaped'single")] - [InlineData("\"Escaped\"\"single\"", "Escaped\"single")] - [InlineData("\"No ending quote", "\"No ending quote")] - [InlineData("No open quote'", "No open quote'")] - [InlineData("'''quoted'''", "'quoted'")] - public void It_unescapes_strings(string input, string result) - { - Assert.Equal(result, SqliteDmlParser.UnescapeString(input)); - } - - [Theory] - [InlineData(',', "a,b,c", new[] { "a", "b", "c" })] - [InlineData(',', "a',' ,b,c", new[] { "a',' ", "b", "c" })] - [InlineData(',', "(a')' ,b),c", new[] { "(a')' ,b)", "c" })] - [InlineData(',', "\",,,\",\",,,\"", new[] { "\",,,\"", "\",,,\"" })] - [InlineData(',', "\",',\",\",',\"", new[] { "\",',\"", "\",',\"" })] - [InlineData(',', @"`~!@#$%^&*()+=-[];'',.<>/?|\", new[] { @"`~!@#$%^&*()+=-[];''", @".<>/?|\" })] - [InlineData(' ', @"CREATE TABLE '`~!@#$%^&*()+=-[];''"",.<>/?|\ ' ", new[] { "CREATE", "TABLE", @"'`~!@#$%^&*()+=-[];''"",.<>/?|\ '" })] - public void It_safely_splits(char sep, string input, string[] results) - { - Assert.Equal(results, SqliteDmlParser.SafeSplit(input, sep)); - } - } -}