From 891d1199bb5a117016756b66df050764fd0ec762 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Wed, 10 Aug 2022 23:33:55 -0700 Subject: [PATCH 01/15] exposed names in request, backing columns in query structure --- src/Service/Parsers/RequestParser.cs | 19 ++++-- .../Sql Query Structures/SqlQueryStructure.cs | 66 ++++++++++++------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/Service/Parsers/RequestParser.cs b/src/Service/Parsers/RequestParser.cs index 9f53899b7d..6fa21b5b98 100644 --- a/src/Service/Parsers/RequestParser.cs +++ b/src/Service/Parsers/RequestParser.cs @@ -164,18 +164,21 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv DataApiBuilderException.SubStatusCodes.BadRequest); string backingColumnName; + string exposedName; if (expression.Kind is QueryNodeKind.SingleValuePropertyAccess) { // if name is in SingleValuePropertyAccess node it matches our model and we will // always be able to get backing column successfully - sqlMetadataProvider.TryGetBackingColumn(context.EntityName, ((SingleValuePropertyAccessNode)expression).Property.Name, out backingColumnName!); + exposedName = ((SingleValuePropertyAccessNode)expression).Property.Name; + sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName!); } else if (expression.Kind is QueryNodeKind.Constant && ((ConstantNode)expression).Value is not null) { // since this comes from constant node, it was not checked against our model // so this may return false in which case we throw for a bad request - if (!sqlMetadataProvider.TryGetBackingColumn(context.EntityName, ((ConstantNode)expression).Value.ToString()!, out backingColumnName!)) + exposedName = ((ConstantNode)expression).Value.ToString()!; + if (!sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName!)) { throw new DataApiBuilderException( message: $"Invalid orderby column requested: {((ConstantNode)expression).Value.ToString()!}.", @@ -185,16 +188,17 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv } else { - throw new DataApiBuilderException(message: "OrderBy property is not supported.", - HttpStatusCode.BadRequest, - DataApiBuilderException.SubStatusCodes.BadRequest); + throw new DataApiBuilderException( + message: "OrderBy property is not supported.", + HttpStatusCode.BadRequest, + DataApiBuilderException.SubStatusCodes.BadRequest); } // Sorting order is stored in node.Direction as OrderByDirection Enum // We convert to an Enum of our own that matches the SQL text we want OrderBy direction = GetDirection(node.Direction); // Add OrderByColumn and remove any matching columns from our primary key set - orderByList.Add(new OrderByColumn(schemaName, tableName, backingColumnName, direction: direction)); + orderByList.Add(new OrderByColumn(schemaName, tableName, exposedName, direction: direction)); remainingKeys.Remove(backingColumnName); node = node.ThenBy; } @@ -206,7 +210,8 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv { if (remainingKeys.Contains(column)) { - orderByList.Add(new OrderByColumn(schemaName, tableName, column)); + sqlMetadataProvider.TryGetBackingColumn(context.EntityName, column, out string? exposedName); + orderByList.Add(new OrderByColumn(schemaName, tableName, exposedName!)); } } diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs index 604601d56d..f953db83f9 100644 --- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -173,7 +173,9 @@ public SqlQueryStructure( // context.OrderByColumnsInUrl will lack TableAlias because it is created in RequestParser // which may be called for any type of operation. To avoid coupling the OrderByClauseInUrl // to only Find, we populate the TableAlias in this constructor where we know we have a Find operation. - OrderByColumns = context.OrderByClauseInUrl is not null ? context.OrderByClauseInUrl : PrimaryKeyAsOrderByColumns(); + OrderByColumns = context.OrderByClauseInUrl is not null ? + GetOrderByBackingColumns(context.OrderByClauseInUrl) : + GetOrderByBackingColumns(PrimaryKeyAsOrderByColumns()); foreach (OrderByColumn column in OrderByColumns) { if (string.IsNullOrEmpty(column.TableAlias)) @@ -230,6 +232,24 @@ public SqlQueryStructure( ParametrizeColumns(); } + /// + /// The OrderByClauseInUrl uses exposed names since it comes from + /// the request. Here we convert this to used backing column for + /// the actual query we will generate. + /// + /// OrderByColumns with exposed names. + /// + private List GetOrderByBackingColumns(List orderByClauseInUrl) + { + foreach (OrderByColumn column in orderByClauseInUrl) + { + SqlMetadataProvider.TryGetBackingColumn(EntityName, column.ColumnName!, out string? backingColumn); + column.ColumnName = backingColumn!; + } + + return orderByClauseInUrl; + } + /// /// Use the mapping of exposed names to /// backing columns to add column with @@ -246,6 +266,28 @@ private void AddFields(RestRequestContext context, ISqlMetadataProvider sqlMetad } } + /// + /// Exposes the primary key of the underlying table of the structure + /// as a list of OrderByColumn + /// + public List PrimaryKeyAsOrderByColumns() + { + if (_primaryKeyAsOrderByColumns == null) + { + _primaryKeyAsOrderByColumns = new(); + + foreach (string column in PrimaryKey()) + { + _primaryKeyAsOrderByColumns.Add(new OrderByColumn(tableSchema: DatabaseObject.SchemaName, + tableName: DatabaseObject.Name, + columnName: column, + tableAlias: TableAlias)); + } + } + + return _primaryKeyAsOrderByColumns; + } + /// /// Private constructor that is used for recursive query generation, /// for each subquery that's necessary to resolve a nested GraphQL @@ -844,28 +886,6 @@ private List ProcessGqlOrderByArg(List orderByFi return orderByColumnsList; } - /// - /// Exposes the primary key of the underlying table of the structure - /// as a list of OrderByColumn - /// - public List PrimaryKeyAsOrderByColumns() - { - if (_primaryKeyAsOrderByColumns == null) - { - _primaryKeyAsOrderByColumns = new(); - - foreach (string column in PrimaryKey()) - { - _primaryKeyAsOrderByColumns.Add(new OrderByColumn(tableSchema: DatabaseObject.SchemaName, - tableName: DatabaseObject.Name, - columnName: column, - tableAlias: TableAlias)); - } - } - - return _primaryKeyAsOrderByColumns; - } - /// /// Adds a labelled column to this query's columns, where /// the column name is all that is provided, and we add From be628bf1d889b1a650cc2159917ee51c50e9dc95 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Thu, 11 Aug 2022 10:40:26 -0700 Subject: [PATCH 02/15] typos --- .../Resolvers/Sql Query Structures/SqlQueryStructure.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs index f953db83f9..b6412d15f6 100644 --- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -234,11 +234,11 @@ public SqlQueryStructure( /// /// The OrderByClauseInUrl uses exposed names since it comes from - /// the request. Here we convert this to used backing column for + /// the request. Here we convert this to use backing column for /// the actual query we will generate. /// /// OrderByColumns with exposed names. - /// + /// OrderByColumns with backing column names. private List GetOrderByBackingColumns(List orderByClauseInUrl) { foreach (OrderByColumn column in orderByClauseInUrl) From 28865d55f651ac5f6a900578e9c6e301ae7ea7ff Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Thu, 11 Aug 2022 11:04:18 -0700 Subject: [PATCH 03/15] dont need to reset primarykeycolumn names --- src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs index b6412d15f6..758269e413 100644 --- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -175,7 +175,7 @@ public SqlQueryStructure( // to only Find, we populate the TableAlias in this constructor where we know we have a Find operation. OrderByColumns = context.OrderByClauseInUrl is not null ? GetOrderByBackingColumns(context.OrderByClauseInUrl) : - GetOrderByBackingColumns(PrimaryKeyAsOrderByColumns()); + PrimaryKeyAsOrderByColumns(); foreach (OrderByColumn column in OrderByColumns) { if (string.IsNullOrEmpty(column.TableAlias)) From 2cbf8132611d0a9f3368e10dd29740a96702dc7b Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Thu, 11 Aug 2022 19:57:10 -0700 Subject: [PATCH 04/15] exposed name for primary key in request --- src/Service/Parsers/RequestParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Parsers/RequestParser.cs b/src/Service/Parsers/RequestParser.cs index 6fa21b5b98..e99ac4169c 100644 --- a/src/Service/Parsers/RequestParser.cs +++ b/src/Service/Parsers/RequestParser.cs @@ -210,7 +210,7 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv { if (remainingKeys.Contains(column)) { - sqlMetadataProvider.TryGetBackingColumn(context.EntityName, column, out string? exposedName); + sqlMetadataProvider.TryGetExposedColumnName(context.EntityName, column, out string? exposedName); orderByList.Add(new OrderByColumn(schemaName, tableName, exposedName!)); } } From 4dece1fff6b2eb6f1e11262d7b8912b6296381ef Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 12:37:42 -0700 Subject: [PATCH 05/15] public to private --- src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs index 758269e413..b8c90c2e6e 100644 --- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -270,7 +270,7 @@ private void AddFields(RestRequestContext context, ISqlMetadataProvider sqlMetad /// Exposes the primary key of the underlying table of the structure /// as a list of OrderByColumn /// - public List PrimaryKeyAsOrderByColumns() + private List PrimaryKeyAsOrderByColumns() { if (_primaryKeyAsOrderByColumns == null) { From 022dce1d39e24c438dcbb1f8ae539a22772e892c Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 13:47:03 -0700 Subject: [PATCH 06/15] add testing --- .../RestApiTests/Find/FindApiTestBase.cs | 127 ++++++++++++++++++ .../RestApiTests/Find/MsSqlFindApiTests.cs | 6 + .../Azure.DataApiBuilder.Service.csproj | 19 +-- 3 files changed, 143 insertions(+), 9 deletions(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs index 414fa97014..c3fab7365d 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs @@ -533,6 +533,22 @@ await SetupAndRunRestApiTest( ); } + /// + /// Tests the REST Api for Find operation for all records. + /// order by title in ascending order. + /// + [TestMethod] + public async Task FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc() + { + await SetupAndRunRestApiTest( + primaryKeyRoute: string.Empty, + queryString: "?$orderby=FancyName", + entity: _integrationMappingDifferentEntity, + sqlQuery: GetQuery(nameof(FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc)), + controller: _restController + ); + } + /// /// Tests the REST Api for Find operation for all records /// when there is a space in the column name. @@ -1357,6 +1373,117 @@ await SetupAndRunRestApiTest( ); } + /// + /// Tests the REST Api for FindById operation with attempts at + /// Sql Injection in the primary key route. + /// + [DataTestMethod] + [DataRow(" WHERE 1=1/*", true)] + [DataRow("id WHERE 1=1/*", true)] + [DataRow(" UNION SELECT * FROM books/*", true)] + [DataRow("id UNION SELECT * FROM books/*", true)] + [DataRow("; SELECT * FROM information_schema.tables/*", true)] + [DataRow("id; SELECT * FROM information_schema.tables/*", true)] + [DataRow("; SELECT * FROM v$version/*", true)] + [DataRow("id; SELECT * FROM v$version/*", true)] + [DataRow("id; DROP TABLE books;/*", true)] + [DataRow(" WHERE 1=1--", false)] + [DataRow("id WHERE 1=1--", false)] + [DataRow(" UNION SELECT * FROM books--", false)] + [DataRow("id UNION SELECT * FROM books--", false)] + [DataRow("; SELECT * FROM information_schema.tables--", false)] + [DataRow("id; SELECT * FROM information_schema.tables--", false)] + [DataRow("; SELECT * FROM v$version--", false)] + [DataRow("id; SELECT * FROM v$version--", false)] + [DataRow("id; DROP TABLE books;--", false)] + public async Task FindByIdTestWithSqlInjectionInPKRoute(string sqlInjection, bool slashStar) + { + string message = slashStar ? "Support for url template with implicit primary key field names is not yet added." : + $"Parameter \"{sqlInjection}\" cannot be resolved as column \"id\" with type \"Int32\"."; + await SetupAndRunRestApiTest( + primaryKeyRoute: $"id/{sqlInjection}", + queryString: $"?$select=id", + entity: _integrationEntityName, + sqlQuery: string.Empty, + controller: _restController, + exception: true, + expectedErrorMessage: message, + expectedStatusCode: HttpStatusCode.BadRequest + ); + } + + /// + /// Tests the REST Api for FindById operation with attempts at + /// Sql Injection in the query string. + /// + [DataTestMethod] + [DataRow(" WHERE 1=1/*")] + [DataRow(" WHERE 1=1--")] + [DataRow("id WHERE 1=1/*")] + [DataRow("id WHERE 1=1--")] + [DataRow(" UNION SELECT * FROM books/*")] + [DataRow(" UNION SELECT * FROM books--")] + [DataRow("id UNION SELECT * FROM books/*")] + [DataRow("id UNION SELECT * FROM books--")] + [DataRow("; SELECT * FROM information_schema.tables/*")] + [DataRow("; SELECT * FROM information_schema.tables--")] + [DataRow("id; SELECT * FROM information_schema.tables/*")] + [DataRow("id; SELECT * FROM information_schema.tables--")] + [DataRow("; SELECT * FROM v$version/*")] + [DataRow("; SELECT * FROM v$version--")] + [DataRow("id; SELECT * FROM v$version/*")] + [DataRow("id; SELECT * FROM v$version--")] + [DataRow("id; DROP TABLE books;")] + public async Task FindByIdTestWithSqlInjectionInQueryString(string sqlInjection) + { + await SetupAndRunRestApiTest( + primaryKeyRoute: "id/5671", + queryString: $"?$select={sqlInjection}", + entity: _integrationEntityName, + sqlQuery: string.Empty, + controller: _restController, + exception: true, + expectedErrorMessage: $"Invalid field to be returned requested: {sqlInjection}", + expectedStatusCode: HttpStatusCode.BadRequest + ); + } + + /// + /// Tests the REST Api for Find operation with attempts at + /// Sql Injection in the query string. + /// + [DataTestMethod] + [DataRow(" WHERE 1=1/*")] + [DataRow(" WHERE 1=1--")] + [DataRow("id WHERE 1=1/*")] + [DataRow("id WHERE 1=1--")] + [DataRow(" UNION SELECT * FROM books/*")] + [DataRow(" UNION SELECT * FROM books--")] + [DataRow("id UNION SELECT * FROM books/*")] + [DataRow("id UNION SELECT * FROM books--")] + [DataRow("; SELECT * FROM information_schema.tables/*")] + [DataRow("; SELECT * FROM information_schema.tables--")] + [DataRow("id; SELECT * FROM information_schema.tables/*")] + [DataRow("id; SELECT * FROM information_schema.tables--")] + [DataRow("; SELECT * FROM v$version/*")] + [DataRow("; SELECT * FROM v$version--")] + [DataRow("id; SELECT * FROM v$version/*")] + [DataRow("id; SELECT * FROM v$version--")] + [DataRow("id; DROP TABLE books;")] + public async Task FindManyTestWithSqlInjectionInQueryString(string sqlInjection) + { + await SetupAndRunRestApiTest( + primaryKeyRoute: string.Empty, + queryString: $"?$select={sqlInjection}", + entity: _integrationEntityName, + sqlQuery: string.Empty, + controller: _restController, + exception: true, + expectedErrorMessage: $"Invalid field to be returned requested: {sqlInjection}", + expectedStatusCode: HttpStatusCode.BadRequest + ); + } + #endregion } } diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs index a1b59097d4..b26ee47bc5 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/MsSqlFindApiTests.cs @@ -253,6 +253,12 @@ public class MsSqlFindApiTests : FindApiTestBase $"ORDER BY title, id " + $"FOR JSON PATH, INCLUDE_NULL_VALUES" }, + { + "FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc", + $"SELECT [treeId], [species] AS [fancyName], [region], [height] FROM { _integrationMappingTable } " + + $"ORDER BY species " + + $"FOR JSON PATH, INCLUDE_NULL_VALUES" + }, { "FindTestWithQueryStringSpaceInNamesOrderByAsc", $"SELECT * FROM { _integrationTableHasColumnWithSpace } " + diff --git a/src/Service/Azure.DataApiBuilder.Service.csproj b/src/Service/Azure.DataApiBuilder.Service.csproj index 7745c75b5e..f0b77b9076 100644 --- a/src/Service/Azure.DataApiBuilder.Service.csproj +++ b/src/Service/Azure.DataApiBuilder.Service.csproj @@ -51,15 +51,16 @@ - - - - - - - - - + + + + + + + + + + From 65107284ce1d9a0ea150c165256034cecf6ab560 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 14:02:46 -0700 Subject: [PATCH 07/15] testing --- .../hawaii-config.MsSql.overrides.json | 398 ++++++++++++++++++ .../hawaii-config.MySql.overrides.json | 394 +++++++++++++++++ .../hawaii-config.PostgreSql.overrides.json | 340 +++++++++++++++ .../RestApiTests/Find/FindApiTestBase.cs | 2 +- .../RestApiTests/Find/MySqlFindApiTests.cs | 11 + .../Find/PostgreSqlFindApiTests.cs | 10 + 6 files changed, 1154 insertions(+), 1 deletion(-) create mode 100644 DataGateway.Service/hawaii-config.MsSql.overrides.json create mode 100644 DataGateway.Service/hawaii-config.MySql.overrides.json create mode 100644 DataGateway.Service/hawaii-config.PostgreSql.overrides.json diff --git a/DataGateway.Service/hawaii-config.MsSql.overrides.json b/DataGateway.Service/hawaii-config.MsSql.overrides.json new file mode 100644 index 0000000000..4ba89b4746 --- /dev/null +++ b/DataGateway.Service/hawaii-config.MsSql.overrides.json @@ -0,0 +1,398 @@ +{ + "$schema": "../../project-hawaii/playground/hawaii.draft-01.schema.json", + "data-source": { + "database-type": "mssql", + "connection-string": "Server=(localdb)\\MSSQLLocalDB;Database=master;Integrated Security=true;Persist Security Info=False;User ID=sa;Password=SSMSark!97TRS;MultipleActiveResultSets=False;Connection Timeout=5;" + }, + "mssql": { + "set-session-context": false + }, + "runtime": { + "rest": { + "enabled": true, + "path": "/api" + }, + "graphql": { + "enabled": true, + "path": "/graphql", + "allow-introspection": true + }, + "host": { + "mode": "development", + "cors": { + "origins": [ "http://localhost:3000" ], + "allow-credentials": false + }, + "authentication": { + "provider": "StaticWebApps" + } + } + }, + "entities": { + "Publisher": { + "source": "publishers", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "many", + "target.entity": "Book" + } + } + }, + "Stock": { + "source": "stocks", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "comics": { + "cardinality": "many", + "target.entity": "Comic", + "source.fields": [ "categoryName" ], + "target.fields": [ "categoryName" ] + } + } + }, + "Book": { + "source": "books", + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "publishers": { + "cardinality": "one", + "target.entity": "Publisher" + }, + "websiteplacement": { + "cardinality": "one", + "target.entity": "BookWebsitePlacement" + }, + "reviews": { + "cardinality": "many", + "target.entity": "Review" + }, + "authors": { + "cardinality": "many", + "target.entity": "Author", + "linking.object": "book_author_link", + "linking.source.fields": [ "book_id" ], + "linking.target.fields": [ "author_id" ] + } + }, + "mappings": { + "id": "id", + "title": "title" + } + }, + "BookWebsitePlacement": { + "source": "book_website_placements", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ + "create", + "update", + { + "action": "delete", + "policy": { + "database": "@claims.id eq @item.id" + }, + "fields": { + "include": [ "*" ] + } + } + ] + } + ], + "relationships": { + "books": { + "cardinality": "one", + "target.entity": "Book" + } + } + }, + "Author": { + "source": "authors", + "rest": { + "route": { + "singular": "/api/Author" + } + }, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "many", + "target.entity": "Book", + "linking.object": "book_author_link" + } + } + }, + "Review": { + "source": "reviews", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "one", + "target.entity": "Book" + } + } + }, + "Comic": { + "source": "comics", + "rest": true, + "graphql": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Broker": { + "source": "brokers", + "graphql": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "WebsiteUser": { + "source": "website_users", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "SupportedType": { + "source": "type_table", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "stocks_price": { + "source": "stocks_price", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Tree": { + "source": "trees", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "species": "Scientific Name", + "region": "United State's Region" + } + }, + "Shrub": { + "source": "trees", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "species": "fancyName" + } + }, + "Fungus": { + "source": "fungi", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "spores": "hazards" + } + }, + "Empty": { + "source": "empty_table", + "rest": true, + "permissions": [ + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "books_view_all": { + "source": "books_view_all", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + } + }, + "stocks_view_selected": { + "source": "stocks_view_selected", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + } + }, + "books_publishers_view_composite": { + "source": "books_publishers_view_composite", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + } + }, + "ArtOfWar": { + "source": "aow", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "*" ] + }, + { + "role": "authenticated", + "actions": [ "*" ] + } + ], + "mappings": { + "DetailAssessmentAndPlanning": "始計", + "WagingWar": "作戰", + "StrategicAttack": "謀攻", + "NoteNum": "┬─┬ノ( º _ ºノ)" + } + } + } +} diff --git a/DataGateway.Service/hawaii-config.MySql.overrides.json b/DataGateway.Service/hawaii-config.MySql.overrides.json new file mode 100644 index 0000000000..514b4f9d0a --- /dev/null +++ b/DataGateway.Service/hawaii-config.MySql.overrides.json @@ -0,0 +1,394 @@ +{ + "$schema": "../../project-hawaii/playground/hawaii.draft-01.schema.json", + "data-source": { + "database-type": "mysql", + "connection-string": "server=localhost;database=master;uid=aaron;Password=MYark!97TRS;Allow User Variables=true" + }, + "mssql": { + "set-session-context": false + }, + "runtime": { + "rest": { + "enabled": true, + "path": "/api" + }, + "graphql": { + "enabled": true, + "path": "/graphql", + "allow-introspection": true + }, + "host": { + "mode": "development", + "cors": { + "origins": [ "http://localhost:5000" ], + "allow-credentials": false + }, + "authentication": { + "provider": "StaticWebApps" + } + } + }, + "entities": { + "Publisher": { + "source": "publishers", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "many", + "target.entity": "Book" + } + } + }, + "Stock": { + "source": "stocks", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "comics": { + "cardinality": "many", + "target.entity": "Comic", + "source.fields": [ "categoryName" ], + "target.fields": [ "categoryName" ] + } + } + }, + "Book": { + "source": "books", + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "publishers": { + "cardinality": "one", + "target.entity": "Publisher" + }, + "websiteplacement": { + "cardinality": "one", + "target.entity": "BookWebsitePlacement" + }, + "reviews": { + "cardinality": "many", + "target.entity": "Review" + }, + "authors": { + "cardinality": "many", + "target.entity": "Author", + "linking.object": "book_author_link", + "linking.source.fields": [ "book_id" ], + "linking.target.fields": [ "author_id" ] + } + }, + "mappings": { + "id": "id", + "title": "title" + } + }, + "BookWebsitePlacement": { + "source": "book_website_placements", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ + "create", + "update", + { + "action": "delete", + "policy": { + "database": "@claims.id eq @item.id" + }, + "fields": { + "include": [ "*" ] + } + } + ] + } + ], + "relationships": { + "books": { + "cardinality": "one", + "target.entity": "Book" + } + } + }, + "Author": { + "source": "authors", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "many", + "target.entity": "Book", + "linking.object": "book_author_link" + } + } + }, + "Review": { + "source": "reviews", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "one", + "target.entity": "Book" + } + } + }, + "Comic": { + "source": "comics", + "rest": true, + "graphql": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Broker": { + "source": "brokers", + "graphql": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "WebsiteUser": { + "source": "website_users", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "SupportedType": { + "source": "type_table", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "stocks_price": { + "source": "stocks_price", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Tree": { + "source": "trees", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "species": "Scientific Name", + "region": "United State's Region" + } + }, + "Shrub": { + "source": "trees", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "species": "fancyName" + } + }, + "Fungus": { + "source": "fungi", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "spores": "hazards" + } + }, + "Empty": { + "source": "empty_table", + "rest": true, + "permissions": [ + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "books_view_all": { + "source": "books_view_all", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + } + }, + "stocks_view_selected": { + "source": "stocks_view_selected", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + } + }, + "books_publishers_view_composite": { + "source": "books_publishers_view_composite", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + } + }, + "ArtOfWar": { + "source": "aow", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "*" ] + }, + { + "role": "authenticated", + "actions": [ "*" ] + } + ], + "mappings": { + "DetailAssessmentAndPlanning": "始計", + "WagingWar": "作戰", + "StrategicAttack": "謀攻", + "NoteNum": "┬─┬ノ( º _ ºノ)" + } + } + } +} diff --git a/DataGateway.Service/hawaii-config.PostgreSql.overrides.json b/DataGateway.Service/hawaii-config.PostgreSql.overrides.json new file mode 100644 index 0000000000..1258fd5d1b --- /dev/null +++ b/DataGateway.Service/hawaii-config.PostgreSql.overrides.json @@ -0,0 +1,340 @@ +{ + "$schema": "../../project-hawaii/playground/hawaii.draft-01.schema.json", + "data-source": { + "database-type": "postgresql", + "connection-string": "Host=localhost;Database=Test;username=postgres;password=121983" + }, + "runtime": { + "rest": { + "enabled": true, + "path": "/api" + }, + "graphql": { + "enabled": true, + "path": "/graphql", + "allow-introspection": true + }, + "host": { + "mode": "development", + "cors": { + "origins": [ "http://localhost:5000" ], + "allow-credentials": false + }, + "authentication": { + "provider": "StaticWebApps" + } + } + }, + "entities": { + "Publisher": { + "source": "publishers", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "many", + "target.entity": "Book" + } + } + }, + "Stock": { + "source": "stocks", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "comics": { + "cardinality": "many", + "target.entity": "Comic", + "source.fields": [ "categoryName" ], + "target.fields": [ "categoryName" ] + } + } + }, + "Book": { + "source": "books", + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "publishers": { + "cardinality": "one", + "target.entity": "Publisher" + }, + "websiteplacement": { + "cardinality": "one", + "target.entity": "BookWebsitePlacement" + }, + "reviews": { + "cardinality": "many", + "target.entity": "Review" + }, + "authors": { + "cardinality": "many", + "target.entity": "Author", + "linking.object": "book_author_link", + "linking.source.fields": [ "book_id" ], + "linking.target.fields": [ "author_id" ] + } + }, + "mappings": { + "id": "id", + "title": "title" + } + }, + "BookWebsitePlacement": { + "source": "book_website_placements", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ + "create", + "update", + { + "action": "delete", + "policy": { + "database": "@claims.id eq @item.id" + }, + "fields": { + "include": [ "*" ] + } + } + ] + } + ], + "relationships": { + "books": { + "cardinality": "one", + "target.entity": "Book" + } + } + }, + "Author": { + "source": "authors", + "rest": true, + "graphql": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "many", + "target.entity": "Book", + "linking.object": "book_author_link" + } + } + }, + "Review": { + "source": "reviews", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "relationships": { + "books": { + "cardinality": "one", + "target.entity": "Book" + } + } + }, + "Comic": { + "source": "comics", + "rest": true, + "graphql": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Broker": { + "source": "brokers", + "graphql": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "read" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "WebsiteUser": { + "source": "website_users", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "stocks_price": { + "source": "stocks_price", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "SupportedType": { + "source": "type_table", + "rest": false, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Tree": { + "source": "trees", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "species": "Scientific Name", + "region": "United State's Region" + } + }, + "Shrub": { + "source": "trees", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "species": "fancyName" + } + }, + "Fungus": { + "source": "fungi", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "create", "read", "update", "delete" ] + }, + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ], + "mappings": { + "spores": "hazards" + } + }, + "Empty": { + "source": "empty_table", + "rest": true, + "permissions": [ + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "ArtOfWar": { + "source": "aow", + "rest": true, + "permissions": [ + { + "role": "anonymous", + "actions": [ "*" ] + }, + { + "role": "authenticated", + "actions": [ "*" ] + } + ], + "mappings": { + "DetailAssessmentAndPlanning": "始計", + "WagingWar": "作戰", + "StrategicAttack": "謀攻", + "NoteNum": "┬─┬ノ( º _ ºノ)" + } + } + } +} diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs index c3fab7365d..ec829341af 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs @@ -542,7 +542,7 @@ public async Task FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc() { await SetupAndRunRestApiTest( primaryKeyRoute: string.Empty, - queryString: "?$orderby=FancyName", + queryString: "?$orderby=fancyName", entity: _integrationMappingDifferentEntity, sqlQuery: GetQuery(nameof(FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc)), controller: _restController diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs index d13c095189..780e2fd3e2 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs @@ -489,6 +489,17 @@ SELECT JSON_ARRAYAGG(JSON_OBJECT('id', id, 'title', title, 'publisher_id', publi LIMIT 100 ) AS subq" }, + { + "FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc", + @" + SELECT JSON_ARRAYAGG(JSON_OBJECT('treeId', treeId, 'fancyName', species, 'region', region, 'height', height)) AS data + FROM ( + SELECT * + FROM " + _integrationMappingEntity + @" + ORDER BY species + LIMIT 100 + ) AS subq" + }, { "FindTestWithQueryStringSpaceInNamesOrderByAsc", @" diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs index eef36b17ae..887f2b2e59 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs @@ -383,6 +383,16 @@ SELECT json_agg(to_jsonb(subq)) AS data ORDER BY title, id ) AS subq" }, + { + "FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc", + @" + SELECT json_agg(to_jsonb(subq)) AS data + FROM ( + SELECT ""treeId"", ""species"" AS ""fancyName"", ""region"", ""height"" + FROM " + _integrationMappingTable + @" + ORDER BY fancyName + ) AS subq" + }, { "FindTestWithFirstAndSpacedColumnOrderBy", @" From 4a897b603832a732c873e3ee075dc8aba4a9cdb4 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 14:04:49 -0700 Subject: [PATCH 08/15] deleted ghost files --- .../hawaii-config.MsSql.overrides.json | 398 ------------------ .../hawaii-config.MySql.overrides.json | 394 ----------------- .../hawaii-config.PostgreSql.overrides.json | 340 --------------- 3 files changed, 1132 deletions(-) delete mode 100644 DataGateway.Service/hawaii-config.MsSql.overrides.json delete mode 100644 DataGateway.Service/hawaii-config.MySql.overrides.json delete mode 100644 DataGateway.Service/hawaii-config.PostgreSql.overrides.json diff --git a/DataGateway.Service/hawaii-config.MsSql.overrides.json b/DataGateway.Service/hawaii-config.MsSql.overrides.json deleted file mode 100644 index 4ba89b4746..0000000000 --- a/DataGateway.Service/hawaii-config.MsSql.overrides.json +++ /dev/null @@ -1,398 +0,0 @@ -{ - "$schema": "../../project-hawaii/playground/hawaii.draft-01.schema.json", - "data-source": { - "database-type": "mssql", - "connection-string": "Server=(localdb)\\MSSQLLocalDB;Database=master;Integrated Security=true;Persist Security Info=False;User ID=sa;Password=SSMSark!97TRS;MultipleActiveResultSets=False;Connection Timeout=5;" - }, - "mssql": { - "set-session-context": false - }, - "runtime": { - "rest": { - "enabled": true, - "path": "/api" - }, - "graphql": { - "enabled": true, - "path": "/graphql", - "allow-introspection": true - }, - "host": { - "mode": "development", - "cors": { - "origins": [ "http://localhost:3000" ], - "allow-credentials": false - }, - "authentication": { - "provider": "StaticWebApps" - } - } - }, - "entities": { - "Publisher": { - "source": "publishers", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "many", - "target.entity": "Book" - } - } - }, - "Stock": { - "source": "stocks", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "comics": { - "cardinality": "many", - "target.entity": "Comic", - "source.fields": [ "categoryName" ], - "target.fields": [ "categoryName" ] - } - } - }, - "Book": { - "source": "books", - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "publishers": { - "cardinality": "one", - "target.entity": "Publisher" - }, - "websiteplacement": { - "cardinality": "one", - "target.entity": "BookWebsitePlacement" - }, - "reviews": { - "cardinality": "many", - "target.entity": "Review" - }, - "authors": { - "cardinality": "many", - "target.entity": "Author", - "linking.object": "book_author_link", - "linking.source.fields": [ "book_id" ], - "linking.target.fields": [ "author_id" ] - } - }, - "mappings": { - "id": "id", - "title": "title" - } - }, - "BookWebsitePlacement": { - "source": "book_website_placements", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ - "create", - "update", - { - "action": "delete", - "policy": { - "database": "@claims.id eq @item.id" - }, - "fields": { - "include": [ "*" ] - } - } - ] - } - ], - "relationships": { - "books": { - "cardinality": "one", - "target.entity": "Book" - } - } - }, - "Author": { - "source": "authors", - "rest": { - "route": { - "singular": "/api/Author" - } - }, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "many", - "target.entity": "Book", - "linking.object": "book_author_link" - } - } - }, - "Review": { - "source": "reviews", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "one", - "target.entity": "Book" - } - } - }, - "Comic": { - "source": "comics", - "rest": true, - "graphql": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "Broker": { - "source": "brokers", - "graphql": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "WebsiteUser": { - "source": "website_users", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "SupportedType": { - "source": "type_table", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "stocks_price": { - "source": "stocks_price", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "Tree": { - "source": "trees", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "species": "Scientific Name", - "region": "United State's Region" - } - }, - "Shrub": { - "source": "trees", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "species": "fancyName" - } - }, - "Fungus": { - "source": "fungi", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "spores": "hazards" - } - }, - "Empty": { - "source": "empty_table", - "rest": true, - "permissions": [ - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "books_view_all": { - "source": "books_view_all", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - } - }, - "stocks_view_selected": { - "source": "stocks_view_selected", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - } - }, - "books_publishers_view_composite": { - "source": "books_publishers_view_composite", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - } - }, - "ArtOfWar": { - "source": "aow", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "*" ] - }, - { - "role": "authenticated", - "actions": [ "*" ] - } - ], - "mappings": { - "DetailAssessmentAndPlanning": "始計", - "WagingWar": "作戰", - "StrategicAttack": "謀攻", - "NoteNum": "┬─┬ノ( º _ ºノ)" - } - } - } -} diff --git a/DataGateway.Service/hawaii-config.MySql.overrides.json b/DataGateway.Service/hawaii-config.MySql.overrides.json deleted file mode 100644 index 514b4f9d0a..0000000000 --- a/DataGateway.Service/hawaii-config.MySql.overrides.json +++ /dev/null @@ -1,394 +0,0 @@ -{ - "$schema": "../../project-hawaii/playground/hawaii.draft-01.schema.json", - "data-source": { - "database-type": "mysql", - "connection-string": "server=localhost;database=master;uid=aaron;Password=MYark!97TRS;Allow User Variables=true" - }, - "mssql": { - "set-session-context": false - }, - "runtime": { - "rest": { - "enabled": true, - "path": "/api" - }, - "graphql": { - "enabled": true, - "path": "/graphql", - "allow-introspection": true - }, - "host": { - "mode": "development", - "cors": { - "origins": [ "http://localhost:5000" ], - "allow-credentials": false - }, - "authentication": { - "provider": "StaticWebApps" - } - } - }, - "entities": { - "Publisher": { - "source": "publishers", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "many", - "target.entity": "Book" - } - } - }, - "Stock": { - "source": "stocks", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "comics": { - "cardinality": "many", - "target.entity": "Comic", - "source.fields": [ "categoryName" ], - "target.fields": [ "categoryName" ] - } - } - }, - "Book": { - "source": "books", - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "publishers": { - "cardinality": "one", - "target.entity": "Publisher" - }, - "websiteplacement": { - "cardinality": "one", - "target.entity": "BookWebsitePlacement" - }, - "reviews": { - "cardinality": "many", - "target.entity": "Review" - }, - "authors": { - "cardinality": "many", - "target.entity": "Author", - "linking.object": "book_author_link", - "linking.source.fields": [ "book_id" ], - "linking.target.fields": [ "author_id" ] - } - }, - "mappings": { - "id": "id", - "title": "title" - } - }, - "BookWebsitePlacement": { - "source": "book_website_placements", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ - "create", - "update", - { - "action": "delete", - "policy": { - "database": "@claims.id eq @item.id" - }, - "fields": { - "include": [ "*" ] - } - } - ] - } - ], - "relationships": { - "books": { - "cardinality": "one", - "target.entity": "Book" - } - } - }, - "Author": { - "source": "authors", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "many", - "target.entity": "Book", - "linking.object": "book_author_link" - } - } - }, - "Review": { - "source": "reviews", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "one", - "target.entity": "Book" - } - } - }, - "Comic": { - "source": "comics", - "rest": true, - "graphql": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "Broker": { - "source": "brokers", - "graphql": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "WebsiteUser": { - "source": "website_users", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "SupportedType": { - "source": "type_table", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "stocks_price": { - "source": "stocks_price", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "Tree": { - "source": "trees", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "species": "Scientific Name", - "region": "United State's Region" - } - }, - "Shrub": { - "source": "trees", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "species": "fancyName" - } - }, - "Fungus": { - "source": "fungi", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "spores": "hazards" - } - }, - "Empty": { - "source": "empty_table", - "rest": true, - "permissions": [ - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "books_view_all": { - "source": "books_view_all", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - } - }, - "stocks_view_selected": { - "source": "stocks_view_selected", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - } - }, - "books_publishers_view_composite": { - "source": "books_publishers_view_composite", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - } - }, - "ArtOfWar": { - "source": "aow", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "*" ] - }, - { - "role": "authenticated", - "actions": [ "*" ] - } - ], - "mappings": { - "DetailAssessmentAndPlanning": "始計", - "WagingWar": "作戰", - "StrategicAttack": "謀攻", - "NoteNum": "┬─┬ノ( º _ ºノ)" - } - } - } -} diff --git a/DataGateway.Service/hawaii-config.PostgreSql.overrides.json b/DataGateway.Service/hawaii-config.PostgreSql.overrides.json deleted file mode 100644 index 1258fd5d1b..0000000000 --- a/DataGateway.Service/hawaii-config.PostgreSql.overrides.json +++ /dev/null @@ -1,340 +0,0 @@ -{ - "$schema": "../../project-hawaii/playground/hawaii.draft-01.schema.json", - "data-source": { - "database-type": "postgresql", - "connection-string": "Host=localhost;Database=Test;username=postgres;password=121983" - }, - "runtime": { - "rest": { - "enabled": true, - "path": "/api" - }, - "graphql": { - "enabled": true, - "path": "/graphql", - "allow-introspection": true - }, - "host": { - "mode": "development", - "cors": { - "origins": [ "http://localhost:5000" ], - "allow-credentials": false - }, - "authentication": { - "provider": "StaticWebApps" - } - } - }, - "entities": { - "Publisher": { - "source": "publishers", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "many", - "target.entity": "Book" - } - } - }, - "Stock": { - "source": "stocks", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "comics": { - "cardinality": "many", - "target.entity": "Comic", - "source.fields": [ "categoryName" ], - "target.fields": [ "categoryName" ] - } - } - }, - "Book": { - "source": "books", - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "publishers": { - "cardinality": "one", - "target.entity": "Publisher" - }, - "websiteplacement": { - "cardinality": "one", - "target.entity": "BookWebsitePlacement" - }, - "reviews": { - "cardinality": "many", - "target.entity": "Review" - }, - "authors": { - "cardinality": "many", - "target.entity": "Author", - "linking.object": "book_author_link", - "linking.source.fields": [ "book_id" ], - "linking.target.fields": [ "author_id" ] - } - }, - "mappings": { - "id": "id", - "title": "title" - } - }, - "BookWebsitePlacement": { - "source": "book_website_placements", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ - "create", - "update", - { - "action": "delete", - "policy": { - "database": "@claims.id eq @item.id" - }, - "fields": { - "include": [ "*" ] - } - } - ] - } - ], - "relationships": { - "books": { - "cardinality": "one", - "target.entity": "Book" - } - } - }, - "Author": { - "source": "authors", - "rest": true, - "graphql": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "many", - "target.entity": "Book", - "linking.object": "book_author_link" - } - } - }, - "Review": { - "source": "reviews", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "relationships": { - "books": { - "cardinality": "one", - "target.entity": "Book" - } - } - }, - "Comic": { - "source": "comics", - "rest": true, - "graphql": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "Broker": { - "source": "brokers", - "graphql": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "read" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "WebsiteUser": { - "source": "website_users", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "stocks_price": { - "source": "stocks_price", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "SupportedType": { - "source": "type_table", - "rest": false, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "Tree": { - "source": "trees", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "species": "Scientific Name", - "region": "United State's Region" - } - }, - "Shrub": { - "source": "trees", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "species": "fancyName" - } - }, - "Fungus": { - "source": "fungi", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "create", "read", "update", "delete" ] - }, - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ], - "mappings": { - "spores": "hazards" - } - }, - "Empty": { - "source": "empty_table", - "rest": true, - "permissions": [ - { - "role": "authenticated", - "actions": [ "create", "read", "update", "delete" ] - } - ] - }, - "ArtOfWar": { - "source": "aow", - "rest": true, - "permissions": [ - { - "role": "anonymous", - "actions": [ "*" ] - }, - { - "role": "authenticated", - "actions": [ "*" ] - } - ], - "mappings": { - "DetailAssessmentAndPlanning": "始計", - "WagingWar": "作戰", - "StrategicAttack": "謀攻", - "NoteNum": "┬─┬ノ( º _ ºノ)" - } - } - } -} From 28619890893ac5b4dbefabfcd66fdb1cea337a4d Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 14:09:56 -0700 Subject: [PATCH 09/15] typo --- .../SqlTests/RestApiTests/Find/MySqlFindApiTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs index 780e2fd3e2..240a9aaea0 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/MySqlFindApiTests.cs @@ -495,7 +495,7 @@ LIMIT 100 SELECT JSON_ARRAYAGG(JSON_OBJECT('treeId', treeId, 'fancyName', species, 'region', region, 'height', height)) AS data FROM ( SELECT * - FROM " + _integrationMappingEntity + @" + FROM " + _integrationMappingTable + @" ORDER BY species LIMIT 100 ) AS subq" From 88aff2e154b3fb956870f02334ade2705d907c54 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 14:14:52 -0700 Subject: [PATCH 10/15] typo --- .../SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs index 887f2b2e59..b6f6bd3454 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs @@ -390,7 +390,7 @@ SELECT json_agg(to_jsonb(subq)) AS data FROM ( SELECT ""treeId"", ""species"" AS ""fancyName"", ""region"", ""height"" FROM " + _integrationMappingTable + @" - ORDER BY fancyName + ORDER BY ""fancyName"" ) AS subq" }, { From 2b4523c33ffdfb66db9b7d832af8a665911fd431 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Fri, 12 Aug 2022 14:24:18 -0700 Subject: [PATCH 11/15] better comment for new test --- src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs index ec829341af..fbe64346e3 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/FindApiTestBase.cs @@ -535,7 +535,7 @@ await SetupAndRunRestApiTest( /// /// Tests the REST Api for Find operation for all records. - /// order by title in ascending order. + /// Uses entity with mapped columns, and order by title in ascending order. /// [TestMethod] public async Task FindTestWithQueryStringAllFieldsMappedEntityOrderByAsc() From 3b8cb1619c2295cf93bd90cce60df75ea3a30362 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 22 Aug 2022 15:56:15 -0700 Subject: [PATCH 12/15] addressing comments --- .../Find/PostgreSqlFindApiTests.cs | 2 +- .../RestRequestContexts/RestRequestContext.cs | 6 +++ src/Service/Parsers/RequestParser.cs | 37 +++++++++++-------- .../Sql Query Structures/SqlQueryStructure.cs | 10 ++--- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs b/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs index b6f6bd3454..bba8a49798 100644 --- a/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs +++ b/src/Service.Tests/SqlTests/RestApiTests/Find/PostgreSqlFindApiTests.cs @@ -390,7 +390,7 @@ SELECT json_agg(to_jsonb(subq)) AS data FROM ( SELECT ""treeId"", ""species"" AS ""fancyName"", ""region"", ""height"" FROM " + _integrationMappingTable + @" - ORDER BY ""fancyName"" + ORDER BY ""species"" ) AS subq" }, { diff --git a/src/Service/Models/RestRequestContexts/RestRequestContext.cs b/src/Service/Models/RestRequestContexts/RestRequestContext.cs index 98c3dd2674..d5eb331e90 100644 --- a/src/Service/Models/RestRequestContexts/RestRequestContext.cs +++ b/src/Service/Models/RestRequestContexts/RestRequestContext.cs @@ -61,6 +61,12 @@ protected RestRequestContext(string entityName, DatabaseObject dbo) /// public virtual List? OrderByClauseInUrl { get; set; } + /// + /// List of OrderBy Columns which represent the OrderByClause using backing columns. + /// Based on the operation type, this property may or may not be populated. + /// + public virtual List? OrderByClauseOfBackingColumns { get; set; } + /// /// Dictionary of field names and their values given in the request body. /// Based on the operation type, this property may or may not be populated. diff --git a/src/Service/Parsers/RequestParser.cs b/src/Service/Parsers/RequestParser.cs index e99ac4169c..8532edc267 100644 --- a/src/Service/Parsers/RequestParser.cs +++ b/src/Service/Parsers/RequestParser.cs @@ -109,7 +109,7 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv break; case SORT_URL: string sortQueryString = $"?{SORT_URL}={context.ParsedQueryString[key]}"; - context.OrderByClauseInUrl = GenerateOrderByList(context, sqlMetadataProvider, sortQueryString); + (context.OrderByClauseInUrl, context.OrderByClauseOfBackingColumns) = GenerateOrderByLists(context, sqlMetadataProvider, sortQueryString); break; case AFTER_URL: context.After = context.ParsedQueryString[key]; @@ -135,9 +135,9 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv /// associated with the sort param. /// A List /// - private static List? GenerateOrderByList(RestRequestContext context, - ISqlMetadataProvider sqlMetadataProvider, - string sortQueryString) + private static (List?, List?) GenerateOrderByLists(RestRequestContext context, + ISqlMetadataProvider sqlMetadataProvider, + string sortQueryString) { string schemaName = context.DatabaseObject.SchemaName; string tableName = context.DatabaseObject.Name; @@ -148,7 +148,9 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv // used for performant Remove operations HashSet remainingKeys = new(primaryKeys); - List orderByList = new(); + List orderByListUrl = new(); + List orderByListBackingColumn = new(); + // OrderBy AST is in the form of a linked list // so we traverse by calling node.ThenBy until // node is null @@ -159,18 +161,19 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv // column name of null. ie: $orderby='hello world', or $orderby=null // note: null support is not currently implemented. QueryNode? expression = node.Expression is not null ? node.Expression : - throw new DataApiBuilderException(message: "OrderBy property is not supported.", - HttpStatusCode.BadRequest, - DataApiBuilderException.SubStatusCodes.BadRequest); + throw new DataApiBuilderException( + message: "OrderBy property is not supported.", + HttpStatusCode.BadRequest, + DataApiBuilderException.SubStatusCodes.BadRequest); - string backingColumnName; + string? backingColumnName; string exposedName; if (expression.Kind is QueryNodeKind.SingleValuePropertyAccess) { // if name is in SingleValuePropertyAccess node it matches our model and we will // always be able to get backing column successfully exposedName = ((SingleValuePropertyAccessNode)expression).Property.Name; - sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName!); + sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName); } else if (expression.Kind is QueryNodeKind.Constant && ((ConstantNode)expression).Value is not null) @@ -178,10 +181,10 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv // since this comes from constant node, it was not checked against our model // so this may return false in which case we throw for a bad request exposedName = ((ConstantNode)expression).Value.ToString()!; - if (!sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName!)) + if (!sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName)) { throw new DataApiBuilderException( - message: $"Invalid orderby column requested: {((ConstantNode)expression).Value.ToString()!}.", + message: $"Invalid orderby column requested: {exposedName}.", statusCode: HttpStatusCode.BadRequest, subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest); } @@ -198,8 +201,9 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv // We convert to an Enum of our own that matches the SQL text we want OrderBy direction = GetDirection(node.Direction); // Add OrderByColumn and remove any matching columns from our primary key set - orderByList.Add(new OrderByColumn(schemaName, tableName, exposedName, direction: direction)); - remainingKeys.Remove(backingColumnName); + orderByListUrl.Add(new OrderByColumn(schemaName, tableName, exposedName, direction: direction)); + orderByListBackingColumn.Add(new OrderByColumn(schemaName, tableName, backingColumnName!, direction: direction)); + remainingKeys.Remove(backingColumnName!); node = node.ThenBy; } @@ -211,11 +215,12 @@ public static void ParseQueryString(RestRequestContext context, ISqlMetadataProv if (remainingKeys.Contains(column)) { sqlMetadataProvider.TryGetExposedColumnName(context.EntityName, column, out string? exposedName); - orderByList.Add(new OrderByColumn(schemaName, tableName, exposedName!)); + orderByListUrl.Add(new OrderByColumn(schemaName, tableName, exposedName!)); + orderByListBackingColumn.Add(new OrderByColumn(schemaName, tableName, column)); } } - return orderByList; + return (orderByListUrl, orderByListBackingColumn); } /// diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs index a5886479f1..c9468dcd2c 100644 --- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -171,12 +171,12 @@ public SqlQueryStructure( value: predicate.Value); } - // context.OrderByColumnsInUrl will lack TableAlias because it is created in RequestParser + // context.OrderByColumnsObackingColumns will lack TableAlias because it is created in RequestParser // which may be called for any type of operation. To avoid coupling the OrderByClauseInUrl // to only Find, we populate the TableAlias in this constructor where we know we have a Find operation. - OrderByColumns = context.OrderByClauseInUrl is not null ? - GetOrderByBackingColumns(context.OrderByClauseInUrl) : - PrimaryKeyAsOrderByColumns(); + OrderByColumns = context.OrderByClauseOfBackingColumns is not null ? + context.OrderByClauseOfBackingColumns : PrimaryKeyAsOrderByColumns(); + foreach (OrderByColumn column in OrderByColumns) { if (string.IsNullOrEmpty(column.TableAlias)) @@ -273,7 +273,7 @@ private void AddFields(RestRequestContext context, ISqlMetadataProvider sqlMetad /// private List PrimaryKeyAsOrderByColumns() { - if (_primaryKeyAsOrderByColumns == null) + if (_primaryKeyAsOrderByColumns is null) { _primaryKeyAsOrderByColumns = new(); From ac6bb020f1063e32c84bd9fb30ab2c2214758228 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 22 Aug 2022 16:11:39 -0700 Subject: [PATCH 13/15] use correct orderbyclause for pagination --- src/Service/Resolvers/SqlQueryEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Resolvers/SqlQueryEngine.cs b/src/Service/Resolvers/SqlQueryEngine.cs index c93ba49326..134b64c177 100644 --- a/src/Service/Resolvers/SqlQueryEngine.cs +++ b/src/Service/Resolvers/SqlQueryEngine.cs @@ -157,7 +157,7 @@ private OkObjectResult FormatFindResult(JsonDocument jsonDoc, FindRequestContext rootEnumerated = rootEnumerated.Take(rootEnumerated.Count() - 1); string after = SqlPaginationUtil.MakeCursorFromJsonElement( element: rootEnumerated.Last(), - orderByColumns: context.OrderByClauseInUrl, + orderByColumns: context.OrderByClauseOfBackingColumns, primaryKey: _sqlMetadataProvider.GetTableDefinition(context.EntityName).PrimaryKey, entityName: context.EntityName, schemaName: context.DatabaseObject.SchemaName, From 09c482c183d82f9926c72626ebd30f96fcd5f2b1 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 22 Aug 2022 16:16:05 -0700 Subject: [PATCH 14/15] cleanup --- .../Sql Query Structures/SqlQueryStructure.cs | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs index c9468dcd2c..7776d62d0b 100644 --- a/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs +++ b/src/Service/Resolvers/Sql Query Structures/SqlQueryStructure.cs @@ -171,8 +171,8 @@ public SqlQueryStructure( value: predicate.Value); } - // context.OrderByColumnsObackingColumns will lack TableAlias because it is created in RequestParser - // which may be called for any type of operation. To avoid coupling the OrderByClauseInUrl + // context.OrderByClauseOfBackingColumns will lack TableAlias because it is created in RequestParser + // which may be called for any type of operation. To avoid coupling the OrderByClauseOfBackingColumns // to only Find, we populate the TableAlias in this constructor where we know we have a Find operation. OrderByColumns = context.OrderByClauseOfBackingColumns is not null ? context.OrderByClauseOfBackingColumns : PrimaryKeyAsOrderByColumns(); @@ -233,24 +233,6 @@ public SqlQueryStructure( ParametrizeColumns(); } - /// - /// The OrderByClauseInUrl uses exposed names since it comes from - /// the request. Here we convert this to use backing column for - /// the actual query we will generate. - /// - /// OrderByColumns with exposed names. - /// OrderByColumns with backing column names. - private List GetOrderByBackingColumns(List orderByClauseInUrl) - { - foreach (OrderByColumn column in orderByClauseInUrl) - { - SqlMetadataProvider.TryGetBackingColumn(EntityName, column.ColumnName!, out string? backingColumn); - column.ColumnName = backingColumn!; - } - - return orderByClauseInUrl; - } - /// /// Use the mapping of exposed names to /// backing columns to add column with From 8f66cd3e8d39ecbefce0af4a7b57e1ef375811c9 Mon Sep 17 00:00:00 2001 From: aaron burtle Date: Mon, 22 Aug 2022 23:02:53 -0700 Subject: [PATCH 15/15] moved comment --- src/Service/Parsers/RequestParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/Parsers/RequestParser.cs b/src/Service/Parsers/RequestParser.cs index 8532edc267..d02ca1f6d3 100644 --- a/src/Service/Parsers/RequestParser.cs +++ b/src/Service/Parsers/RequestParser.cs @@ -170,10 +170,10 @@ private static (List?, List?) GenerateOrderByLists string exposedName; if (expression.Kind is QueryNodeKind.SingleValuePropertyAccess) { - // if name is in SingleValuePropertyAccess node it matches our model and we will - // always be able to get backing column successfully exposedName = ((SingleValuePropertyAccessNode)expression).Property.Name; sqlMetadataProvider.TryGetBackingColumn(context.EntityName, exposedName, out backingColumnName); + // if name is in SingleValuePropertyAccess node it matches our model and we will + // always be able to get backing column successfully } else if (expression.Kind is QueryNodeKind.Constant && ((ConstantNode)expression).Value is not null)