Create complex argument-driven queries from AWS Lambda? - aws-lambda

Look for // HERE IS THE PROBLEM PART sentence to find code that is the problem.
I am trying to implement AppSync using AWS Lambda (that connects to RDS Postgres server) as a data source. I want to create puKnowledgeFile query that will update my KnowledgeFile with optional arguments. If the client only provided htmlText and properties as arguments, then my update query should only update these two fields.
type Mutation {
putKnowledgeFile(
id: ID!,
htmlText: String,
plainText: String,
properties: AWSJSON
): KnowledgeFile
}
type KnowledgeFile {
id: ID!
htmlText: String!
plainText: String!
properties: AWSJSON!
lastDateTimeModified: AWSDateTime!
dateTimeCreated: AWSDateTime!
}
Here is an piece of AWS Lambda code:
exports.handler = async (event, context, callback) => {
/* Connecting to Postgres */
let data = null;
let query = ``;
let values = [];
switch (event.info.fieldName) {
case "putKnowledgeFile":
if(event.arguments.htmlText === undefined &&
event.arguments.plainText === undefined &&
event.arguments.properties === undefined) {
callback(`At least one argument except id should be provided in putKnowledgeFile request`);
}
// HERE IS THE PROBLEM PART
query += `update knowledge_file`
query += `
set `;
let index = 0;
for (let fieldName in event.arguments) {
if(arguments.hasOwnProperty(fieldName)) {
const fieldValue = event.arguments[fieldName];
if(index === 0) {
query += `${fieldName}=$${index+1}`
values.push(fieldValue);
} else {
query += `, ${fieldName}=$${index+1}`
values.push(fieldValue);
}
index++;
}
}
query += `
where knowledge_file.id = $${index+1};`;
values.push(event.arguments.id);
// HERE IS THE PROBLEM PART
break;
default:
callback(`There is no functionality to process this field: ${event.info.fieldName}`);
return;
}
let res = null;
try {
res = await client.query(query, values); // just sending created query
} catch(error) {
console.log("#client.query");
console.log(error);
}
/* DisConnecting from Postgres */
callback(null, res.rows);
};
Basically, this algorithm creates my query string through multiple string concatenations. I think it's too complicated and error-prone. Is there a way to create dynamic queries based on the presence / absence of certain arguments easily?
Just in case, here is my PostgreSQL schema:
-- main client object for clients
CREATE TABLE client (
id bigserial primary key,
full_name varchar(255)
);
-- knowledge_file
create table knowledge_file (
id bigserial primary key,
html_text text,
plain_text text,
properties jsonb,
last_date_modified timestamptz,
date_created timestamptz,
word_count varchar(50)
);
-- which client holds which knowledge file
create TABLE client_knowledge_file (
id bigserial primary key,
client_id bigint not null references client(id),
knowledge_file_id bigint not null references knowledge_file(id) unique ON DELETE CASCADE
);

I know this is not an optimum solution and might not completely answer your question but I also ran into similar problem and this is how I solved it.
I created a resolver pipeline.
In one function, I used the select statement to get the current
record.
In second function, I checked if the fields (in your case htmlText and properties) are null. If true, then use the ctx.prev.result values otherwise use the new ones).
Practical example
First resolver function:
{
"version": "2018-05-29",
"statements": [
"select id, html_text AS \"htmlText\", plain_text AS \"plainText\", properties, last_date_modified AS \"lastDateTimeModified\", date_created AS \"dateTimeCreated\" from knowledge_file where id = $ctx.args.Id"
]
}
Second resolver function:
#set($htmlText = $util.defaultIfNull($ctx.args.htmlText , $ctx.prev.result.htmlText))
#set($properties = $util.defaultIfNull($ctx.args.properties , $ctx.prev.result.properties))
{
"version": "2018-05-29",
"statements": [
"update knowledge_file set html_text = $htmlText, plain_text = $ctx.args.plainText, properties = $properties, last_date_modified = CURRENT_TIMESTAMP, date_created = CURRENT_DATE where id = $ctx.args.Id returning id, html_text AS \"htmlText\", plain_text AS \"plainText\", properties, last_date_modified AS \"lastDateTimeModified\", date_created AS \"dateTimeCreated\""
]
}

Related

The properties of the file for records generated by jooq are always set to Optional

I have created the User table and generateJooq generates UserRecord.kt, but always the properties of the UserRecord class are optional.
I would like to know how to avoid sharing optional properties.
By the way, the schema of the User table is Not Null.
DB schema
CREATE TABLE user
(
id varchar(256) UNIQUE NOT NULL,
email varchar(256) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
build.gradle.kts
:
jooq {
configurations {
create("main") {
jooqConfiguration.apply {
jdbc.apply {
url = System.getenv("DB_URL")
user = System.getenv("DB_USER")
password = System.getenv("DB_PASS")
}
generator.apply {
name = "org.jooq.codegen.KotlinGenerator"
database.apply {
name = "org.jooq.meta.mysql.MySQLDatabase"
inputSchema = System.getenv("DB_NAME")
excludes = "flyway_schema_history"
}
generate.apply {
isDeprecated = false
isTables = true
isComments = true
}
target.apply {
packageName = "com.example.infra.jooq"
directory = "${buildDir}/generated/source/jooq/main"
}
}
}
}
}
}
Generated UserRecord.kt
:
/**
* This class is generated by jOOQ.
*/
#Suppress("UNCHECKED_CAST")
open class UserRecord() : UpdatableRecordImpl<UserRecord>(User.USER), Record2<String?, String?> {
var id: String?
set(value): Unit = set(0, value)
get(): String? = get(0) as String?
var email: String?
set(value): Unit = set(1, value)
get(): String? = get(1) as String?
:
}
Technologies
JOOQ: 3.16.4
Gradle
Spring Boot: 3.0.1
This feature will ship with jOOQ 3.18, see:
https://github.com/jOOQ/jOOQ/issues/10212
You can configure:
<kotlinNotNullPojoAttributes>true</kotlinNotNullPojoAttributes>
<kotlinNotNullRecordAttributes>true</kotlinNotNullRecordAttributes>
<kotlinNotNullInterfaceAttributes>true</kotlinNotNullInterfaceAttributes>
Of course, beware that as documented in the above issue, and throughout jOOQ's github issues, Stack Overflow, etc., the guarantee of non-nullability in this case will be a "lie." When jOOQ creates a new UserRecord for you, then those valuse will still be null:
// Contains nulls!
val user: UserRecord = ctx.newRecord(USER)

Store error: the application attempted to write an object with no provided typename but the store already contains an object

After mutation when I am updating the cache, changes are reflected in UI but getting the below error
Invariant Violation: Store error: the application attempted to write an object with no provided typename but the store already contains an object with typename of ItemCodeConnection for the object of id $ROOT_QUERY.itemCodes({"filter":{"number":10000001}}). The selectionSet that was trying to be written is:
{"kind":"Field","name":{"kind":"Name","value":"itemCodes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"itemCodes"},"arguments":[],"directives":[],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"itemCodeTile"},"directives":[]},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}}
GraphQL query:
const CREATE_ITEM_CODE_SPEC = gql`
mutation createItemCodeSpec($input: createItemCodeSpecInput) {
createItemCodeSpecification(input: $input){
__typename
id
itemCode {
number
}
product
spec_class
grade
}
}
`
const GET_ITEM_CODE = gql`
query itemCode($filter: filterInput){
itemCodes(filter: $filter){
itemCodes {
number
type
description
group
item_code_spec {
id
itemCode {
number
}
product
spec_class
grade
}
created_on
created_by
changed_on
changed_by
}
}
}
`
Below is the mutation:
const [mutation, { data, loading, error}] = useMutation(
CREATE_ITEM_CODE_SPEC,
{
update(cache, { data: { createItemCodeSpecification } }){
const currentData = cache.readQuery({
query: GET_ITEM_CODE,
variables: { filter : {number:itemCode} }
})
cache.writeQuery({
query: GET_ITEM_CODE,
variables: { filter : {number:itemCode} },
data: {
...currentData,
itemCodes: {
itemCodes: currentData.itemCodes.itemCodes.map((itemCode, index) => {
return {
...itemCode,
item_code_spec: index === 0? [
...itemCode.item_code_spec,
createItemCodeSpecification
] : itemCode.item_code_spec
}
})
}
}
})
}
}
);
You simply need to add "id" for each subsection of your query. Adding "id" for "itemCodes" in your GET_ITEM_CODE query might solve your problem.
You have fields missing in your response mutation.
Basically, you should make your mutation results have all of the data necessary to update the queries previously fetched.
That’s also why is a best practice to use fragments to share fields among all queries and mutations that are related.
To make it work both query and mutation should have exactly the same fields.
Have a look here to see more in depth how cache updates work:
https://medium.com/free-code-camp/how-to-update-the-apollo-clients-cache-after-a-mutation-79a0df79b840

Find where results in columns from an array of column names in TypeORM

At the moment I have the following query:
return await this.siteRepository.find({
where: [{ id: Like(`%${q}%`) }, { name: Like(`%${q}%`) }]
});
But I would like to be able to pass a list of column names to be used for the query from an array and not write each one of them manually.
const columns = ["id","name", "lastName", "age"]
const query = {};
return await this.siteRepository.find({
where: columns.map(column=>{(query[`${column}`] = `Like("%${q}%")}`)})
});
Is this even possible? I'm starting to feel like it currently is not.
I didn't manage to accomplish what I wanted with the Repository TypeORM methods but I did manage to do it with the QueryBuilder
Here is my solution
const res = ['id', 'name'].map(item => `${item} LIKE :q`).join(' OR ');
return await this.siteRepository
.createQueryBuilder()
.where(res, { q: `%${q}%` })
.getMany();
Which yields a query of
SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`lastName` AS `User_lastName`, `User`.`active` AS `User_active` FROM `user` `User` WHERE name LIKE ? OR id LIKE ?

How can i search by field of joined table in graphql and nestjs

i create two table tag and tagTranslation.
following is field of each
Tag
id, type, transloations, creaed_at, updated_at
TagTranslation
id, tag_id, name, language
I use graphql, i want to get tag list by type, name and language
{ tags(name:"tag1", language:"en, type:3){
id,
type,
translations{
id,
name,
language,
}
}
}
so I create resolver like following
#Query(returns => [Tag])
tags(#Args() tagArgs: TagArgs): Promise<Tag[]> {
const where = {
...(tagArgs.type) && {type: tagArgs.type}
};
const include_where = {
...(tagArgs.name) && {name: { [Op.like]: `%${tagArgs.name}%` }},
...(tagArgs.language) && {language: tagArgs.language}
};
return this.tagService.findAll({
where: where,
include: {
as: 'translations',
model: TagTranslation,
where: include_where,
required: true,
}
});
}
#Query(returns => Tag)
tag(#Args({name: 'id', type: ()=> Int}) id: number): Promise<Tag>{
return this.tagService.get(id)
}
#ResolveProperty()
async translations(#Parent() tag): Promise<TagTranslation[]>{
const { id } = tag;
return await this.tagTranslationService.findAll({tag_id: id});
}
when i call tags, the query is called twice
first, A query is executed to get the results I want.
but second,
SELECT `id`, `tag_id`, `name`, `language`, `created_at`, `updated_at` FROM `tag_translation` AS `TagTranslation` WHERE `TagTranslation`.`tag_id` = 1;
query is called once more, so i can't get results what i want.
I think second query is called because of ResolveProperty, I remove ResolveProperty. after that, tag query is not include tagtranslation info...
how can i solve that problem ? or is there another idea??
how can i solve that problem ? or is there another idea??
Relations between entities should be resolved on a field resolver (#ResolveProperty()) level because when someone requests only id and type, you will still perform additional, not needed join on TagTranslation in sql query.

How to return nested objects in GraphQL schema language

I was going through the documentation for GraphQl and realized that the new Schema Langugage supports only default resolvers. Is there a way I can add custom resolvers while using the new Schema Language?
let userObj = {
id: 1,
name: "A",
homeAddress: {
line1: "Line1",
line2: "Line2",
city: "City"
}
};
let schema = buildSchema(`
type Query {
user(id: ID): User
}
type User {
id: ID
name: String
address: String
}
`);
//I would like User.address to be resolved from the fields in the json response eg. address = Line1, Line2, City
This is the schema that I have defined. I would like to add some behavior here that would allow me to parse the address object and return a concatenated string value.
As mentioned by HagaiCo and in this github issue, the right way would be to go with graphql-tools.
It has a function called makeExecutableSchema, which takes a schema and resolve functions, and then returns an executable schema
It seems like you have a confusion in here, since you defined that address is String but you send a dictionary to resolve it.
what you can do, is to define a scalar address type:
scalar AddressType if you use buildSchema and then attach parse functions to it. (or use graphql-tools to do it easily)
or build the type from scratch like shown in the official documentations:
var OddType = new GraphQLScalarType({
name: 'Odd',
serialize: oddValue,
parseValue: oddValue,
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return oddValue(parseInt(ast.value, 10));
}
return null;
}
});
function oddValue(value) {
return value % 2 === 1 ? value : null;
}
and then you can parse the dictionary into a string (parseValue) and otherwise

Resources