-
Notifications
You must be signed in to change notification settings - Fork 301
Create and execute query to return tables and interpolated names for Autoentities #3082
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
255b59b
12ee205
b6f5f5b
8439c6e
97eaf7a
e1aa3a5
c72e20e
90cdf7b
6a585ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -506,7 +506,7 @@ public string BuildStoredProcedureResultDetailsQuery(string databaseObjectName) | |
| /// 2. are computed based on other columns, | ||
| /// are considered as read only columns. The query combines both the types of read-only columns and returns the list. | ||
| /// </summary> | ||
| /// <param name="schemaOrDatabaseParamName">Param name of the schema/database.</param> | ||
| /// <param name="schemaParamName">Param name of the schema/database.</param> | ||
| /// <param name="tableParamName">Param name of the table.</param> | ||
| /// <returns></returns> | ||
| public string BuildQueryToGetReadOnlyColumns(string schemaParamName, string tableParamName) | ||
|
|
@@ -560,5 +560,39 @@ protected override string BuildPredicates(SqlQueryStructure structure) | |
| // contains LIKE and add the ESCAPE clause accordingly. | ||
| return AddEscapeToLikeClauses(predicates); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Builds the query used to get the list of tables with the SQL LIKE | ||
| /// syntax that will be transformed into entities. | ||
| /// NOTE: Currently this query only returns Tables, support for Views will come later. | ||
| /// </summary> | ||
| /// <param name="include">Pattern for tables that will be included.</param> | ||
| /// <param name="exclude">Pattern for tables that will be excluded.</param> | ||
| /// <param name="namePattern">Pattern for naming the entities.</param> | ||
| public string BuildGetAutoentitiesQuery(string include, string exclude, string namePattern) | ||
| { | ||
| string query = $"DECLARE @include_pattern NVARCHAR(MAX) = '{include}'; DECLARE @exclude_pattern NVARCHAR(MAX) = '{exclude}'; DECLARE @name_pattern NVARCHAR(255) = '{namePattern}'; " + | ||
| "DECLARE @exclude_invalid_types BIT = 1; SET NOCOUNT ON; WITH include_patterns AS ( SELECT LTRIM(RTRIM(value)) AS pattern " + | ||
| "FROM STRING_SPLIT(ISNULL(@include_pattern, N''), N',') WHERE LTRIM(RTRIM(value)) <> N'' ), " + | ||
| "exclude_patterns AS ( SELECT LTRIM(RTRIM(value)) AS pattern FROM STRING_SPLIT(ISNULL(@exclude_pattern, N''), N',') " + | ||
| "WHERE LTRIM(RTRIM(value)) <> N'' ), all_tables AS ( SELECT s.name AS schema_name, t.name AS object_name, s.name + N'.' " + | ||
| "+ t.name AS full_name, N'table' AS object_type, t.object_id FROM sys.tables AS t JOIN sys.schemas AS s ON t.schema_id = s.schema_id " + | ||
| "WHERE EXISTS ( SELECT 1 FROM sys.key_constraints AS kc WHERE kc.parent_object_id = t.object_id AND kc.type = 'PK' ) ), eligible_tables AS " + | ||
| "( SELECT o.schema_name, o.object_name, o.full_name, o.object_type, o.object_id, CASE WHEN so.is_ms_shipped = 1 THEN 1 WHEN o.schema_name IN " + | ||
| "(N'sys', N'INFORMATION_SCHEMA') THEN 1 WHEN o.object_name IN ( N'__EFMigrationsHistory', N'__MigrationHistory', N'__FlywayHistory', N'sysdiagrams' ) THEN 1 " + | ||
| "WHEN o.object_name LIKE N'service_broker_%' THEN 1 WHEN o.object_name LIKE N'queue_messages_%' THEN 1 WHEN o.object_name LIKE N'MSmerge_%' " + | ||
| "THEN 1 WHEN o.object_name LIKE N'MSreplication_%' THEN 1 WHEN o.object_name LIKE N'FileTableUpdates$%' THEN 1 WHEN o.object_name LIKE N'graph_%' THEN 1 WHEN EXISTS " + | ||
| "( SELECT 1 FROM sys.tables AS t WHERE t.object_id = o.object_id AND ( t.is_tracked_by_cdc = 1 OR t.temporal_type > 0 OR t.is_filetable = 1 OR t.is_memory_optimized = 1 ) ) " + | ||
| "THEN 1 ELSE 0 END AS is_system_object FROM all_tables AS o JOIN sys.objects AS so ON so.object_id = o.object_id ) SELECT a.schema_name AS [schema], a.object_name AS [object], " + | ||
| "CASE WHEN LTRIM(RTRIM(ISNULL(@name_pattern, N''))) = N'' THEN a.object_name ELSE REPLACE( REPLACE(@name_pattern, N'{schema}', a.schema_name), N'{object}', a.object_name ) " + | ||
| "END AS entity_name, CASE WHEN EXISTS ( SELECT 1 FROM sys.columns AS c JOIN sys.types AS ty ON c.user_type_id = ty.user_type_id WHERE c.object_id = a.object_id AND ty.name IN " + | ||
| "( N'geography', N'geometry', N'hierarchyid', N'sql_variant', N'xml', N'rowversion', N'vector' ) ) THEN 1 ELSE 0 END AS contains_invalid_types FROM eligible_tables AS a WHERE " + | ||
| "a.is_system_object = 0 AND ( NOT EXISTS (SELECT 1 FROM exclude_patterns) OR NOT EXISTS ( SELECT 1 FROM exclude_patterns AS ep WHERE a.full_name LIKE ep.pattern " + | ||
| "COLLATE DATABASE_DEFAULT ESCAPE '\\' ) ) AND ( NOT EXISTS (SELECT 1 FROM include_patterns) OR EXISTS ( SELECT 1 FROM include_patterns AS ip WHERE a.full_name LIKE ip.pattern " + | ||
| "COLLATE DATABASE_DEFAULT ESCAPE '\\' ) ) AND ( @exclude_invalid_types = 0 OR NOT EXISTS ( SELECT 1 FROM sys.columns AS c JOIN sys.types AS ty ON c.user_type_id = ty.user_type_id " + | ||
| "WHERE c.object_id = a.object_id AND ty.name IN ( N'geography', N'geometry', N'hierarchyid', N'sql_variant', N'xml', N'rowversion', N'vector' ) ) ) ORDER BY a.schema_name, a.object_name;"; | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The query currently has ~8 tightly‑packed clauses, which makes the SQL hard to read and review. Splitting the logic into layered Common Table Expressions (WITH blocks) makes each step clear: normalize include patterns -> normalize exclude patterns -> gather all tables -> filter eligible objects -> filter invalid types -> final SELECT. |
||
| return query; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -307,6 +307,11 @@ public string GetEntityName(string graphQLType) | |
| public async Task InitializeAsync() | ||
| { | ||
| System.Diagnostics.Stopwatch timer = System.Diagnostics.Stopwatch.StartNew(); | ||
| if (GetDatabaseType() == DatabaseType.MSSQL) | ||
| { | ||
| await GenerateAutoentitiesIntoEntities(); | ||
| } | ||
|
Comment on lines
307
to
+313
|
||
|
|
||
| GenerateDatabaseObjectForEntities(); | ||
| if (_isValidateOnly) | ||
| { | ||
|
|
@@ -686,6 +691,15 @@ private void GenerateDatabaseObjectForEntities() | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates entities for each table that is found, based on the autoentity configuration. | ||
| /// This method is only called for tables in MsSql. | ||
| /// </summary> | ||
| protected virtual Task GenerateAutoentitiesIntoEntities() | ||
| { | ||
| throw new NotSupportedException($"{GetType().Name} does not support Autoentities yet."); | ||
| } | ||
|
|
||
| protected void PopulateDatabaseObjectForEntity( | ||
| Entity entity, | ||
| string entityName, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -588,5 +588,68 @@ private static async Task SetupTestFixtureAndInferMetadata() | |
| await ResetDbStateAsync(); | ||
| await _sqlMetadataProvider.InitializeAsync(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Ensures that the query that returns the tables that will be generated | ||
| /// into entities from the autoentities configuration returns the expected result. | ||
| /// </summary> | ||
| [DataTestMethod, TestCategory(TestCategory.MSSQL)] | ||
| [DataRow(new string[] { "dbo.%book%" }, new string[] { }, "{schema}.{object}.books", new string[] { "book" }, "")] | ||
| [DataRow(new string[] { "dbo.%publish%" }, new string[] { }, "{schema}.{object}", new string[] { "publish" }, "")] | ||
| [DataRow(new string[] { "dbo.%book%" }, new string[] { "dbo.%books%" }, "{schema}_{object}_exclude_books", new string[] { "book" }, "books")] | ||
| [DataRow(new string[] { "dbo.%book%", "dbo.%publish%" }, new string[] { }, "{object}", new string[] { "book", "publish" }, "")] | ||
| [DataRow(new string[] { }, new string[] { "dbo.%book%" }, "{object}", new string[] { "" }, "book")] | ||
| public async Task CheckAutoentitiesQuery(string[] include, string[] exclude, string name, string[] includeObject, string excludeObject) | ||
RubenCerna2079 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| // Arrange | ||
| DatabaseEngine = TestCategory.MSSQL; | ||
| TestHelper.SetupDatabaseEnvironment(DatabaseEngine); | ||
| RuntimeConfig runtimeConfig = SqlTestHelper.SetupRuntimeConfig(); | ||
| Autoentity autoentity = new(new AutoentityPatterns(include, exclude, name), null, null); | ||
| Dictionary<string, Autoentity> dictAutoentity = new() | ||
| { | ||
| { "autoentity", autoentity } | ||
| }; | ||
| RuntimeConfig configWithAutoentity = runtimeConfig with | ||
| { | ||
| Autoentities = new RuntimeAutoentities(dictAutoentity) | ||
| }; | ||
| RuntimeConfigProvider runtimeConfigProvider = TestHelper.GenerateInMemoryRuntimeConfigProvider(configWithAutoentity); | ||
| SetUpSQLMetadataProvider(runtimeConfigProvider); | ||
|
|
||
| await _sqlMetadataProvider.InitializeAsync(); | ||
|
|
||
| // Act | ||
| MsSqlMetadataProvider metadataProvider = (MsSqlMetadataProvider)_sqlMetadataProvider; | ||
| JsonArray resultArray = await metadataProvider.QueryAutoentitiesAsync(autoentity); | ||
|
|
||
| // Assert | ||
| Assert.IsNotNull(resultArray); | ||
| foreach(JsonObject resultObject in resultArray) | ||
| { | ||
| bool includedObjectExists = false; | ||
| foreach (string included in includeObject) | ||
| { | ||
| if (resultObject["object"].ToString().Contains(included)) | ||
| { | ||
| includedObjectExists = true; | ||
| Assert.AreNotEqual(name, resultObject["entity_name"].ToString(), "Name returned by query should not include {schema} or {object}."); | ||
| if (include.Length > 0) | ||
| { | ||
| Assert.AreEqual(expected: "dbo", actual: resultObject["schema"].ToString(), "Query does not return expected schema."); | ||
| } | ||
|
|
||
| if (exclude.Length > 0) | ||
| { | ||
| Assert.IsTrue(!resultObject["object"].ToString().Contains(excludeObject), "Query returns pattern that should be excluded."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Assert.IsTrue(includedObjectExists, "Query does not return expected object."); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test checks only |
||
| } | ||
|
|
||
| TestHelper.UnsetAllDABEnvironmentVariables(); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.