-
Notifications
You must be signed in to change notification settings - Fork 349
Expand file tree
/
Copy pathSqlUpdateQueryStructure.cs
More file actions
197 lines (181 loc) · 8.57 KB
/
Copy pathSqlUpdateQueryStructure.cs
File metadata and controls
197 lines (181 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Net;
using Azure.DataApiBuilder.Auth;
using Azure.DataApiBuilder.Config.DatabasePrimitives;
using Azure.DataApiBuilder.Config.ObjectModel;
using Azure.DataApiBuilder.Core.Models;
using Azure.DataApiBuilder.Core.Services;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Mutations;
using HotChocolate.Resolvers;
using Microsoft.AspNetCore.Http;
namespace Azure.DataApiBuilder.Core.Resolvers
{
///<summary>
/// Wraps all the required data and logic to write a SQL UPDATE query
///</summary>
public class SqlUpdateStructure : BaseSqlQueryStructure
{
/// <summary>
/// Updates to be applied to selected row
/// </summary>
public List<Predicate> UpdateOperations { get; }
/// <summary>
/// The columns used for OUTPUT
/// </summary>
public List<LabelledColumn> OutputColumns { get; }
public SqlUpdateStructure(
string entityName,
ISqlMetadataProvider sqlMetadataProvider,
IAuthorizationResolver authorizationResolver,
GQLFilterParser gQLFilterParser,
IDictionary<string, object?> mutationParams,
HttpContext httpContext,
bool isIncrementalUpdate)
: base(
metadataProvider: sqlMetadataProvider,
authorizationResolver: authorizationResolver,
gQLFilterParser: gQLFilterParser,
entityName: entityName,
httpContext: httpContext,
operationType: EntityActionOperation.Update)
{
UpdateOperations = new();
OutputColumns = GenerateOutputColumns();
SourceDefinition sourceDefinition = GetUnderlyingSourceDefinition();
List<string> primaryKeys = sourceDefinition.PrimaryKey;
List<string> columns = sourceDefinition.Columns.Keys.ToList();
foreach (KeyValuePair<string, object?> param in mutationParams)
{
Predicate predicate = CreatePredicateForParam(param);
// since we have already validated mutationParams we know backing column exists
MetadataProvider.TryGetBackingColumn(EntityName, param.Key, out string? backingColumn);
// primary keys used as predicates
if (primaryKeys.Contains(backingColumn!))
{
Predicates.Add(predicate);
}
// use columns to determine values to edit
else if (columns.Contains(backingColumn!))
{
UpdateOperations.Add(predicate);
}
columns.Remove(backingColumn!);
}
if (!isIncrementalUpdate)
{
AddNullifiedUnspecifiedFields(columns, UpdateOperations, sourceDefinition);
}
if (UpdateOperations.Count == 0)
{
throw new DataApiBuilderException(
message: "Update mutation does not update any values",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
}
/// <summary>
/// This constructor is for GraphQL updates which have UpdateEntityInput item
/// as one of the mutation params.
/// </summary>
public SqlUpdateStructure(
IMiddlewareContext context,
string entityName,
ISqlMetadataProvider sqlMetadataProvider,
IAuthorizationResolver authorizationResolver,
GQLFilterParser gQLFilterParser,
IDictionary<string, object?> mutationParams,
HttpContext httpContext)
: base(
metadataProvider: sqlMetadataProvider,
authorizationResolver: authorizationResolver,
gQLFilterParser: gQLFilterParser,
entityName: entityName,
httpContext: httpContext,
operationType: EntityActionOperation.Update)
{
UpdateOperations = new();
SourceDefinition sourceDefinition = GetUnderlyingSourceDefinition();
List<string> columns = sourceDefinition.Columns.Keys.ToList();
OutputColumns = GenerateOutputColumns();
foreach (KeyValuePair<string, object?> param in mutationParams)
{
// primary keys used as predicates
string pkBackingColumn = param.Key;
if (sqlMetadataProvider.TryGetBackingColumn(entityName, param.Key, out string? name) && !string.IsNullOrWhiteSpace(name))
{
pkBackingColumn = name;
}
if (sourceDefinition.PrimaryKey.Contains(pkBackingColumn))
{
Predicates.Add(CreatePredicateForParam(new KeyValuePair<string, object?>(pkBackingColumn, param.Value)));
}
else // Unpack the input argument type as columns to update
if (param.Key == UpdateAndPatchMutationBuilder.INPUT_ARGUMENT_NAME)
{
IDictionary<string, object?> updateFields =
GQLMutArgumentToDictParams(context, UpdateAndPatchMutationBuilder.INPUT_ARGUMENT_NAME, mutationParams);
foreach (KeyValuePair<string, object?> field in updateFields)
{
string fieldBackingColumn = field.Key;
if (sqlMetadataProvider.TryGetBackingColumn(entityName, field.Key, out string? resolvedBackingColumn)
&& !string.IsNullOrWhiteSpace(resolvedBackingColumn))
{
fieldBackingColumn = resolvedBackingColumn;
}
if (columns.Contains(fieldBackingColumn))
{
UpdateOperations.Add(CreatePredicateForParam(new KeyValuePair<string, object?>(key: fieldBackingColumn, field.Value)));
}
}
}
}
if (UpdateOperations.Count == 0)
{
throw new DataApiBuilderException(
message: "Update mutation does not update any values",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
}
private Predicate CreatePredicateForParam(KeyValuePair<string, object?> param)
{
SourceDefinition sourceDefinition = GetUnderlyingSourceDefinition();
Predicate predicate;
// since we have already validated param we know backing column exists
MetadataProvider.TryGetBackingColumn(EntityName, param.Key, out string? backingColumn);
if (backingColumn is null)
{
// If param.Key was not present in the ExposedToBackingColumnMap then provided param.Key is already the backing column
backingColumn = param.Key;
}
if (param.Value is null && !sourceDefinition.Columns[backingColumn].IsNullable)
{
throw new DataApiBuilderException(
message: $"Cannot set argument {param.Key} to null.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
else if (param.Value is null)
{
predicate = new(
new PredicateOperand(
new Column(tableSchema: DatabaseObject.SchemaName, tableName: DatabaseObject.Name, backingColumn)),
PredicateOperation.Equal,
new PredicateOperand($"{MakeDbConnectionParam(null, backingColumn)}")
);
}
else
{
string stringValue = GetStringifiedValue(param.Value);
predicate = new(
new PredicateOperand(
new Column(tableSchema: DatabaseObject.SchemaName, tableName: DatabaseObject.Name, backingColumn)),
PredicateOperation.Equal,
new PredicateOperand($"{MakeDbConnectionParam(GetParamAsSystemType(stringValue, backingColumn, GetColumnSystemType(backingColumn)), backingColumn)}"));
}
return predicate;
}
}
}