Skip to content

Commit 9ee03c0

Browse files
authored
Primary Key Parameters for GraphQL Queries (#116)
Added parameter support for primary key search for graphql queries. Added tests for the new feature
1 parent 427d662 commit 9ee03c0

File tree

7 files changed

+176
-9
lines changed

7 files changed

+176
-9
lines changed

DataGateway.Service.Tests/SqlTests/MsSqlGraphQLQueryTests.cs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,61 @@ ORDER BY [id]
211211
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
212212
}
213213

214+
[TestMethod]
215+
public async Task QueryWithSingleColumnPrimaryKey()
216+
{
217+
string graphQLQueryName = "getBook";
218+
string graphQLQuery = @"{
219+
getBook(id: 2) {
220+
title
221+
}
222+
}";
223+
string msSqlQuery = @"
224+
SELECT title FROM books
225+
WHERE id = 2 FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER
226+
";
227+
228+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
229+
string expected = await GetDatabaseResultAsync(msSqlQuery);
230+
231+
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
232+
}
233+
234+
[TestMethod]
235+
public async Task QueryWithMultileColumnPrimaryKey()
236+
{
237+
string graphQLQueryName = "getReview";
238+
string graphQLQuery = @"{
239+
getReview(id: 568, book_id: 1) {
240+
content
241+
}
242+
}";
243+
string msSqlQuery = @"
244+
SELECT TOP 1 content FROM reviews
245+
WHERE id = 568 AND book_id = 1 FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER
246+
";
247+
248+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
249+
string expected = await GetDatabaseResultAsync(msSqlQuery);
250+
251+
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
252+
}
253+
254+
[TestMethod]
255+
public async Task QueryWithNullResult()
256+
{
257+
string graphQLQueryName = "getBook";
258+
string graphQLQuery = @"{
259+
getBook(id: -9999) {
260+
title
261+
}
262+
}";
263+
264+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
265+
266+
SqlTestHelper.PerformTestEqualJsonStrings("null", actual);
267+
}
268+
214269
#endregion
215270

216271
#region Query Test Helper Functions
@@ -232,7 +287,9 @@ public static async Task<string> GetGraphQLResultAsync(string graphQLQuery, stri
232287
JsonDocument graphQLResult = await _graphQLController.PostAsync();
233288
Console.WriteLine(graphQLResult.RootElement.ToString());
234289
JsonElement graphQLResultData = graphQLResult.RootElement.GetProperty("data").GetProperty(graphQLQueryName);
235-
return graphQLResultData.ToString();
290+
291+
// JsonElement.ToString() prints null values as empty strings instead of "null"
292+
return graphQLResultData.GetRawText();
236293
}
237294

238295
#endregion

DataGateway.Service.Tests/SqlTests/PostgreSqlGraphQLQueryTests.cs

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ LIMIT 100
108108
) AS subq5
109109
";
110110

111-
string expected = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
112-
string actual = await GetDatabaseResultAsync(postgresQuery);
111+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
112+
string expected = await GetDatabaseResultAsync(postgresQuery);
113113

114114
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
115115
}
@@ -205,6 +205,73 @@ LIMIT 100
205205
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
206206
}
207207

208+
[TestMethod]
209+
public async Task QueryWithSingleColumnPrimaryKey()
210+
{
211+
string graphQLQueryName = "getBook";
212+
string graphQLQuery = @"{
213+
getBook(id: 2) {
214+
title
215+
}
216+
}";
217+
string postgresQuery = @"
218+
SELECT to_jsonb(subq) AS data
219+
FROM (
220+
SELECT table0.title AS title
221+
FROM books AS table0
222+
WHERE id = 2
223+
ORDER BY id
224+
LIMIT 1
225+
) AS subq
226+
";
227+
228+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
229+
string expected = await GetDatabaseResultAsync(postgresQuery);
230+
231+
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
232+
}
233+
234+
[TestMethod]
235+
public async Task QueryWithMultileColumnPrimaryKey()
236+
{
237+
string graphQLQueryName = "getReview";
238+
string graphQLQuery = @"{
239+
getReview(id: 568, book_id: 1) {
240+
content
241+
}
242+
}";
243+
string postgresQuery = @"
244+
SELECT to_jsonb(subq) AS data
245+
FROM (
246+
SELECT table0.content AS content
247+
FROM reviews AS table0
248+
WHERE id = 568 AND book_id = 1
249+
ORDER BY id, book_id
250+
LIMIT 1
251+
) AS subq
252+
";
253+
254+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
255+
string expected = await GetDatabaseResultAsync(postgresQuery);
256+
257+
SqlTestHelper.PerformTestEqualJsonStrings(expected, actual);
258+
}
259+
260+
[TestMethod]
261+
public async Task QueryWithNullResult()
262+
{
263+
string graphQLQueryName = "getBook";
264+
string graphQLQuery = @"{
265+
getBook(id: -9999) {
266+
title
267+
}
268+
}";
269+
270+
string actual = await GetGraphQLResultAsync(graphQLQuery, graphQLQueryName);
271+
272+
SqlTestHelper.PerformTestEqualJsonStrings("null", actual);
273+
}
274+
208275
#endregion
209276

210277
#region Query Test Helper Functions
@@ -228,7 +295,9 @@ public static async Task<string> GetGraphQLResultAsync(string graphQLQuery, stri
228295
JsonDocument graphQLResult = await _graphQLController.PostAsync();
229296
Console.WriteLine(graphQLResult.RootElement.ToString());
230297
JsonElement graphQLResultData = graphQLResult.RootElement.GetProperty("data").GetProperty(graphQLQueryName);
231-
return graphQLResultData.ToString();
298+
299+
// JsonElement.ToString() prints null values as empty strings instead of "null"
300+
return graphQLResultData.GetRawText();
232301
}
233302

234303
#endregion

DataGateway.Service.Tests/SqlTests/SqlTestBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ protected static async Task<string> GetDatabaseResultAsync(string queryText)
9393
{
9494
using DbDataReader reader = await _queryExecutor.ExecuteQueryAsync(queryText, parameters: null);
9595

96+
// an empty result will cause an error with the json parser
97+
if (!reader.HasRows)
98+
{
99+
throw new System.Exception("No rows to read from database result");
100+
}
101+
96102
using JsonDocument sqlResult = JsonDocument.Parse(await SqlQueryEngine.GetJsonStringFromDbReader(reader));
97103

98104
JsonElement sqlResultData = sqlResult.RootElement;

DataGateway.Service/Resolvers/SqlQueryEngine.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static async Task<string> GetJsonStringFromDbReader(DbDataReader dbDataRe
5252
/// </summary>
5353
public async Task<JsonDocument> ExecuteAsync(IMiddlewareContext context, IDictionary<string, object> parameters)
5454
{
55-
SqlQueryStructure structure = new(context, _metadataStoreProvider, _queryBuilder);
55+
SqlQueryStructure structure = new(context, parameters, _metadataStoreProvider, _queryBuilder);
5656
return await ExecuteAsync(structure);
5757
}
5858

@@ -62,7 +62,7 @@ public async Task<JsonDocument> ExecuteAsync(IMiddlewareContext context, IDictio
6262
/// </summary>
6363
public async Task<IEnumerable<JsonDocument>> ExecuteListAsync(IMiddlewareContext context, IDictionary<string, object> parameters)
6464
{
65-
SqlQueryStructure structure = new(context, _metadataStoreProvider, _queryBuilder);
65+
SqlQueryStructure structure = new(context, parameters, _metadataStoreProvider, _queryBuilder);
6666
Console.WriteLine(structure.ToString());
6767
using DbDataReader dbDataReader = await _queryExecutor.ExecuteQueryAsync(structure.ToString(), structure.Parameters);
6868

DataGateway.Service/Resolvers/SqlQueryStructure.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public class SqlQueryStructure
111111
/// Generate the structure for a SQL query based on GraphQL query
112112
/// information.
113113
/// </summary>
114-
public SqlQueryStructure(IResolverContext ctx, IMetadataStoreProvider metadataStoreProvider, IQueryBuilder queryBuilder)
114+
public SqlQueryStructure(IResolverContext ctx, IDictionary<String, object> queryParams, IMetadataStoreProvider metadataStoreProvider, IQueryBuilder queryBuilder)
115115
// This constructor simply forwards to the more general constructor
116116
// that is used to create GraphQL queries. We give it some values
117117
// that make sense for the outermost query.
@@ -129,7 +129,9 @@ public SqlQueryStructure(IResolverContext ctx, IMetadataStoreProvider metadataSt
129129
// all subqueries in this query.
130130
new IncrementingInteger()
131131
)
132-
{ }
132+
{
133+
AddPrimaryKeyPredicates(queryParams);
134+
}
133135

134136
/// <summary>
135137
/// Generate the structure for a SQL query based on FindRequestContext,
@@ -252,6 +254,31 @@ public void AddColumn(string columnName)
252254
Columns.Add(columnName, column);
253255
}
254256

257+
///<summary>
258+
/// Adds predicates for the primary keys in the paramters of the graphql query
259+
///</summary>
260+
void AddPrimaryKeyPredicates(IDictionary<string, object> queryParams)
261+
{
262+
// queryParams for list queries are not used as primary keys
263+
if (!IsListQuery)
264+
{
265+
List<string> primaryKey = GetTableDefinition().PrimaryKey;
266+
foreach (KeyValuePair<string, object> parameter in queryParams)
267+
{
268+
// do not handle non primary key query parameters for now
269+
if (!primaryKey.Contains(parameter.Key))
270+
{
271+
Console.WriteLine($"Skipping {parameter.Key}");
272+
continue;
273+
}
274+
275+
string parameterName = $"param{Counter.Next()}";
276+
Parameters.Add(parameterName, parameter.Value);
277+
Predicates.Add($"{QualifiedColumn(parameter.Key)} = @{parameterName}");
278+
}
279+
}
280+
}
281+
255282
/// <summary>
256283
/// AddGraphqlFields looks at the fields that are selected in the
257284
/// GraphQL query and all the necessary elements to the query which are

DataGateway.Service/books.gql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
type Query {
22
getBooks(first: Int = 3): [Book!]!
3+
getBook(id: Int!): Book
4+
getReview(id: Int!, book_id: Int!): Review
35
}
46

57
type Publisher {

DataGateway.Service/sql-config.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
"QueryResolvers": [
55
{
66
"Id": "getBooks"
7+
},
8+
{
9+
"Id": "getBook"
10+
},
11+
{
12+
"Id": "getReview"
713
}
814
],
915
"GraphqlTypes": {
@@ -84,7 +90,7 @@
8490
}
8591
},
8692
"reviews": {
87-
"PrimaryKey": ["id"],
93+
"PrimaryKey": ["book_id", "id"],
8894
"Columns": {
8995
"id": {
9096
"Type": "bigint"

0 commit comments

Comments
 (0)