Query DynamoDB by GSI using AppSync SDK and Amplify CLI - amazon-dynamodb

I'm trying to query a DynamoDB table through AWS AppSync, using AWS'
Android AppSync SDK, and the Amplify CLI. I'm querying a Global
Secondary Index (GSI). I'm getting an error:
Expression block '$[query]' requires an expression
I've found a similar
issue which suggests
the fix below. However, it doesn't work for me:
My schema.graphql is:
type olatexOrders #model
#key(fields: ["PK", "SK"])
#key(name: "GSI-item-1", fields: ["GSIPKitem1", "GSISKitem1" ], queryField: "olatexOrdersByGSIPKitem1AndGSISKitem1")
#key(name: "GSI-order-1", fields: ["GSIPKorder1", "GSISKorder1" ], queryField: "olatexOrdersByGSIPKorder1AndGSISKorder1")
{
PK: String!
SK: String!
GSIPKitem1: String
GSISKitem1: String
GSIPKorder1: String
GSISKorder1: String
... and many more fields not relevant for this case
}
The hash key (primary key + secondary key) and both GSIs (GSI-item-1,
GSI-order1) were created correctly in DynamoDB. I'm also able to query my
DynamoDB table from AppSync using GSI:
query MyQuery {
olatexOrdersByGSIPKorder1AndGSISKorder1(GSIPKorder1: "IN_PROGRESS") {
nextToken
items {
GSIPKorder1
GSISKorder1
}
}
}
However, it doesn't work when I try to use autogenerated amplify classes inside
my Android app, as below:
private void query() {
AwsClientFactory.getInstance(context)
.query(OlatexOrdersByGsipKorder1AndGsisKorder1Query
.builder()
.gSIPKorder1(ORDER_STATUS_IN_PROGRESS)
.limit(200)
.build())
.responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
.enqueue(callback);
}
I'm getting the same error mentioned above. After reading the related
issue, my understanding is that there is some
bug/inconsistency/limitation in the way GSIs are implemented in
AppSync and for that reason you need to specify not only the primary key of
the GSI but the sort key and sort order as well. With this knowledge,
for testing, I've rewrite my function to:
private void query() {
AwsClientFactory.getInstance(context)
.query(OlatexOrdersByGsipKorder1AndGsisKorder1Query
.builder()
.gSIPKorder1(ORDER_STATUS_IN_PROGRESS)
.gSISKorder1(ModelStringKeyConditionInput.builder().beginsWith("K").build())
.sortDirection(ModelSortDirection.DESC)
.limit(200)
.build())
.responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
.enqueue(callback);
}
Unfortunately, I'm still getting same error:
Expression block '$[query]' requires an expression
I'm using Amplify CLI version 4.27.2.
All help will be appreciated!
EDIT 1
I've tried to simplified my case. I've created GSI having only one column. Please see schema.graphql below:
type olatexOrders #model
#key(fields: ["PK", "SK"])
#key(name: "GSI-item-1", fields: ["GSIPKitem1"], queryField: "olatexOrdersByGSIItem")
#key(name: "GSI-order-1", fields: ["GSIPKorder1"], queryField: "olatexOrdersByGSIOrder")
{
PK: String!
SK: String!
GSIPKitem1: String
GSIPKorder1: String
... and many more fields not relevant for this case
}
Now I'm trying below code to query my dynamo table via Amplify & AppSync:
public class GetInProgressOrders {
private GraphQLCall.Callback<OlatexOrdersByGsiOrderQuery.Data> callback = new GraphQLCall.Callback<OlatexOrdersByGsiOrderQuery.Data>() {
#Override
public void onResponse(#Nonnull Response<OlatexOrdersByGsiOrderQuery.Data> response) {
try{
Log.d("MyTest", "TST response error: "+errors.get(0).message());
}
catch (Exception e){
Log.e("MyTest", e.getMessage())
}
}
#Override
public void onFailure(#Nonnull ApolloException e) {
Log.e("MyTest", e.getMessage())
}
};
private void query(Context context){
AWSAppSyncClient awsClient = AwsClientFactory.getInstance(context);
OlatexOrdersByGsiOrderQuery tmpQuery = OlatexOrdersByGsiOrderQuery
.builder()
.gSIPKorder1(ORDER_STATUS_IN_PROGRESS)
.build();
awsClient.query(
tmpQuery
)
.responseFetcher(AppSyncResponseFetchers.NETWORK_ONLY)
.enqueue(callback);
}
}
Executing above ends up with same error as previously:
TST response error: Expression block '$[query]' requires an expression
That gives me feeling that I'm doing something significantly wrong. Basically I'm unable to query table in Amplify via GSI. Unfortunately I don't see my mistake.
Regards

That will not exactly be the answer but the workaround that actually works.
It turned out, I had multiple issues, some documented better, some worse. Things I did to fix my codes:
For communication from Android App to AWS AppSync use Amplify class instead using AWSAppSyncClient class.
In schema.graphql don't use fields with capital letters only (in my case, instead using PK & SK use pk & sk)
In schema.graphql don't create types that starts with lowercase
Create id column of ID! type (even if you don't need it at all)
With all above being said, see my schema.graphql that acctually works:
type OlatexOrders #model
#key(fields: ["pk", "sk"])
#key(name: "GSI-item-1", fields: ["gsi_pk_item_1", "gsi_sk_item_1"], queryField: "olatexOrdersByGSIItem")
#key(name: "GSI-order-1", fields: ["gsi_pk_order_1", "gsi_sk_order_1"], queryField: "olatexOrdersByGSIOrder")
{
pk: String!
sk: String!
gsi_pk_item_1: String
gsi_sk_item_1: String
gsi_pk_order_1: String
gsi_sk_order_1: String
... different not relevant fields
id: ID!
}
And my query in Android App:
Amplify.API.query(
ModelQuery.list(OlatexOrders.class, OlatexOrders.GSI_PK_ORDER_1.eq(ORDER_STATUS_IN_PROGRESS)),
response -> {
if(response.hasErrors())
Log.i("TestTag", "Errors: " + response.getErrors().get(0));
if(response.hasData()){
for(OlatexOrders orders: response.getData()){
inProgressOrdersNames.add(orders.getGsiSkOrder_1());
}
}
else{
Log.i("TestTag", "No data");
}
},
error -> Log.e("TestTag", "Error", error)
);
Regards!

Related

Asp.net core WebAPI route with optional parameter only not working

I'm trying to create a GET-Route which takes 4 optional Parameter. There are no required Parameter.
My Route looking like that
[Produces("application/json")]
[HttpGet("SearchWhatever", Name = "GetWhatever")]
public IEnumerable<TmpObject> SearchWhatever(long? eid= null, long? pid = null, string name= null, string firstname= null)
{
//do Smth
}
Basically the "eid" and the "pid" are working as intended, they're completely optional. However the strings are not working as "optional".
If I'm calling the API like "../SearchWhatever?eid=6610232513694" I'll receive the following error:
{
errors: {
name: [
"The name field is required."
],
firstname: [
"The firstname field is required."
]
},
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1",
title: "One or more validation errors occurred.",
status: 400,
traceId: "00-e4eb5dc9bb266e44abda734d6a411e44-5f5a40de7fc10540-00"
}
How do I achieve my goal? Is it even possible? I thought giving a string a default value like null makes the parameter optional already.
Thanks in advance
Here is a demo worked,firstly
add this to your controller:
#nullable enable
action(use string? name,string? firstname,because you use nullable enable,so the string type can be null):
[HttpGet]
[Route("SearchWhatever")]
public IEnumerable<String> SearchWhatever(long? eid, long? pid,string? name,string? firstname)
{
return new List<String> { "success" };
//do Smth
}
result:

SqlException: Invalid column name 'NormalizedName'. Invalid column name 'ConcurrencyStamp'. Invalid column name 'NormalizedName'

Why am I getting exception when using PasswordSignInAsync on .NET Core Identity? Microsoft.AspNetCore.Identity
await _signInManager.PasswordSignInAsync(user.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);
An unhandled exception occurred while processing the request.
SqlException: Invalid column name 'NormalizedName'.
Invalid column name 'ConcurrencyStamp'.
Invalid column name 'NormalizedName'.
Microsoft.Data.SqlClient.SqlCommand+<>c.<ExecuteDbDataReaderAsync>b__164_0(Task<SqlDataReader> result)
I can create user, reset password and so on. 'NormalizedName' is not even part of Microsoft.AspNetCore.Identity. All the columns exist in my table
select LockoutEnd, TwoFactorEnabled, PhoneNumberConfirmed, PhoneNumber, ConcurrencyStamp, SecurityStamp,
PasswordHash, EmailConfirmed, NormalizedEmail, Email, NormalizedUserName, UserName, Id, LockoutEnabled, AccessFailedCount
from [dbo].[AspNetUsers]
The exception is actually coming from the AspNetRoles table and not the AspNetUsers one. If you've migrated an old Asp.Net Identity to Asp.Net Core, then there's two new fields you need to add to the Roles table. Here's a migration for that:
public partial class Identity : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "NormalizedName",
table: "AspNetRoles",
type: "nvarchar(256)",
maxLength: 256,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ConcurrencyStamp",
table: "AspNetRoles",
type: "nvarchar(max)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ConcurrencyStamp",
table: "AspNetRoles");
migrationBuilder.DropColumn(
name: "NormalizedName",
table: "AspNetRoles");
}
}
If you want the full Identity migration including the users, claims and other tables, you can refer to my answer to this question: https://stackoverflow.com/a/65003440/1348324

Response status code does not indicate success when inserting documents

Have have following partion id on my container:
/vesselId
I am trying to add a collection of this object:
public class CoachVessel
{
[JsonProperty("id")]
public string vesselId { get; set; }
[JsonProperty("imo")]
public long Imo { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
}
This is my code to bulk insert the documents:
CosmosClientOptions options = new CosmosClientOptions() { AllowBulkExecution = true };
CosmosClient cosmosclient = new CosmosClient(connStr, options);
Container container = cosmosclient.GetContainer("CoachAPI", "Vessels");
List<Task> concurrentTasks = new List<Task>();
foreach (var vessel in vessels.Take(1))
{
concurrentTasks.Add(container.CreateItemAsync(vessel, new PartitionKey(vessel.vesselId)));
}
await Task.WhenAll(concurrentTasks);
I get following error that does not provide much information?
Microsoft.Azure.Cosmos.CosmosException: 'Response status code does not
indicate success: BadRequest (400); Substatus: 1001; ActivityId: ;
Reason: ();'
Any pointers to what causes this? This is my settings:
I have same problem when deleting documents:
CosmosClientOptions options = new CosmosClientOptions() { AllowBulkExecution = true, MaxRetryAttemptsOnRateLimitedRequests=1000};
CosmosClient cosmosclient = new CosmosClient(connStr, options);
Container container = cosmosclient.GetContainer("CoachAPI", "Vessels");
var allItemsQuery = container.GetItemQueryIterator<string>("SELECT * FROM c.id");
List<Task> concurrentDeleteTasks = new List<Task>();
while (allItemsQuery.HasMoreResults)
{
foreach (var item in await allItemsQuery.ReadNextAsync())
{
concurrentDeleteTasks.Add(container.DeleteItemAsync<string>(item, new PartitionKey("id")));
}
}
await Task.WhenAll(concurrentDeleteTasks.Take(3));
Throws following error:
'Response status code does not indicate success: NotFound (404); Substatus: 0;
The partition key must match a property in the document body. Change the partition key for the container to be, /id and fix your deletion code to correctly specify the partition key. E.g.,
concurrentDeleteTasks.Add(container.DeleteItemAsync<string>(item, new PartitionKey(item.Id)));
I'm from the CosmosDB Engineering Team. From your question, you've defined the partition key on the container as /vesselId, whereas the document has mapped the vesselId to the "id" property in the CoachVessel class. Is this intentional?
If the optional PartitionKey is specified in the CreateItemAsync API, then it needs to match the partition key in the Document. If you intended "id" to be the partition key, then you need to define your container's partition key as "id", not "vesselId". In this case, if the container's partition key is indeed /vesselId, the code expects a property "vesselId" in the input document set to the value vessel.vesselId specified in the partition key. It looks like the "vesselId" property is missing in your input document.
In my case the below error was because I'd updated the .Net SDK from v2 to v3, which no longer auto-generates IDs if one isn't passed.
Microsoft.Azure.Cosmos.CosmosException: 'Response status code does not
indicate success: BadRequest (400); Substatus: 1001; ActivityId: ;
Reason: ();'
I use the repository pattern, so just added a check before calling CreateItemAsync:
if (item.Id == null)
{
item.Id = Guid.NewGuid().ToString();
}

GraphQL SPQR - How to get the list of the fields were requested by client using a query

is there a way to retrieve the list of the fields were requested in a GraphQL query by a client?
Lets assume I have the following types:
type Book {
isbn: String
title: String
genre: String
author: Author
}
type Author {
name: String
surname: String
age: Int
}
Is there a way on Java side inside a method annotated with #GraphQLQuery to know the fields were requested by the client?
For example, having the following queries:
query {
book ( isbn = "12345" ) {
title
genre
}
}
query {
book ( isbn = "12345" ) {
title
author {
name
surname
}
}
}
I can know the first query requested the fields title and genre of Book
and the second required title from Book and also name and surname of the Author ?
Thanks
Massimo
You can obtain this info via #GraphQLEnvironment. If you just need the immediate subfields, you can inject the names like this:
#GraphQLQuery
public Book book(String isbn, #GraphQLEnvironment Set<String> subfields) {
...
}
If you want all the subfields, or other selection info, you can inject ResolutionEnvironment and get DataFetcherEnvironment from there. That in turn gives you access to the whole SelectionSet.
#GraphQLQuery
public Book book(String isbn, #GraphQLEnvironment ResolutionEnvironment env) {
DataFetchingFieldSelectionSet selection = env.dataFetchingEnvironment.getSelectionSet();
...
}

DocumentDb DateTimeOffset string

I have documents with string fields that contain DateTimeOffset values. For example:
public class DateTimePocoDocument : Resource
{
public string startTime { get; set; }
public string endTime { get; set; }
}
Imagine a string value being set as follows.
myDateTimePocoDocument.startTime = DateTimeOffset.UtcNow.ToString("o");
Documents are created in DocumentDb using the .NET DocumentClient.
public async Task<Document> InsertAsync(TDocument data)
{
return await Client.CreateDocumentAsync(Collection.SelfLink, data);
}
Viewing the document in DocumentDb shows the string fields properly stored.
[
{
"startTime": "2016-10-01T13:00:00.0000000+00:00",
"endTime": "2016-10-01T14:35:17.215947+00:00",
"id": "2b6e53e1-2099-41f8-8405-f9daf750cfc8",
"_rid": "6qt9AJ0xkgDkAwAAAAAAAA==",
"_self": "dbs/6qt9AA==/colls/6qt9AJ0xkgA=/docs/6qt9AJ0xkgDkAwAAAAAAAA==/",
"_etag": "\"3d00c96d-0000-0000-0000-586e67b40000\"",
"_attachments": "attachments/",
"_ts": 1483630513
}
]
I do this because I want to manually handle all serialization and deserialization of DateTimeOffset values. I need precision and predictability as data moves across controllers, gets serialized to Azure App client, gets serialized into SQLite and back, etc, etc.
When I execute a query as follows:
Client.CreateDocumentQuery<TDocument>(Collection.DocumentsLink,
query,
new FeedOptions { EnableScanInQuery = true, EnableCrossPartitionQuery = false });
The documents return the startTime string above as "10/01/2016 13:00:00". I've created a custom JsonConverter and attached it to the property to see what was being assigned to the string property. The converter confirms that a DateTime is being assigned to the string field. The DocumentDb client is choosing to treat the string as a date value because it looks like a date. Unfortunately that results in a changed string value in this case. Why is it performing that translation on my string and how can I prevent that without having to customize the string?
Thanks
Looks like Azure Cosmos DB doesn't support DateTimeOffset type yet.
The request for such support is already filed and can be tracked here.

Resources