How to use transactGet in dynamoDB with muliple parametres - node.js

I have a table name 'user' with have partition key 'hashid'.
TABLE STRUCTURE(user)
{
"createtime": "2020-03-06 13:43:12",
"hashid": "x_dt",
"labdata": "x",
"status": "stopped",
"updatetime": "2020-03-06 13:43:12"
}
and code is
var AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({
region: 'ap-south-1',
maxRetries: 13,
retryDelayOptions: { base: 200 }
});
let params = {
TransactItems: [
{
Get: {
TableName: 'user',
Key: {
"hashid":"x_dt",
}
}
}
]
};
let transactionRequest = await docClient.transactGet(params);
It is working fine when I used only 1 argument(hashid). Now, I want to filter based on status.
how to use the status variable?

Related

Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId

I want to do inheritance in Hyperledger Fabric Chaincode using NodeJs.
I have created two classes CommonContract and AdminContract. CommonContract is the base class and AdminContract child class. But I got error when I invoke getLastPatiendId function from AdminContract. Error is as follows.
Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId
[nodemon] app crashed - waiting for file changes before starting...
Even though getLastPatientId function is written in contract it is giving error function does not exists.
Below are the code of AdminContract, CommonContract, index.js file for chaincode and the server API which invoke transaction
CommonContract.js
This is CommonContract smart contract which is a base class.
'use strict';
const { Contract } = require('fabric-contract-api');
class CommonContract extends Contract {
async initLedger(ctx) {
console.info('============= START : Initialize Ledger ===========');
const initData = [
{
"firstName": "ABC",
"middleName": "D",
"lastName": "BCA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
},
{
"firstName": "Five",
"middleName": ".H.",
"lastName": "CDA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
}
]
for (let i = 0; i < initData.length; i++) {
initData[i].docType = 'patient';
await ctx.stub.putState('PID' + i, Buffer.from(JSON.stringify(initData[i])));
console.log('Data Added:---', initData[i]);
}
}
async getPatient(ctx, patientId){
const patient = await ctx.stub.getState(patientId);
if(patient.length || patient.length > 0)
console.log(patient);
let data = JSON.parse(patient.toString());
return data;
}
async getAllPatient(ctx){
const startKey = '';
const endKey = '';
const allResults = [];
for await (const {key, value} of ctx.stub.getStateByRange(startKey, endKey)){
const strValue = Buffer.from(value).toString('utf8');
let record;
try {
record = JSON.parse(strValue);
} catch (err) {
console.log(err);
record = strValue;
}
allResults.push({ Key: key, Record: record });
}
console.info(allResults);
return JSON.stringify(allResults);
}
}
module.exports = CommonContract;
AdminContract.js
This is an AdminContract smart contract that inherits CommonContract and has a single function getLastPatientId() it generally returns the patient id of the last patient created in the ledger.
'use strict';
let Patient = require('./PatientAssets.js');
const CommonContract = require('./CommonContract.js');
class AdminContract extends CommonContract {
async getLastPatientId(ctx) {
let result = await getAllPatient(ctx);
console.log(result);
return result[result.length - 1].patiendId;
}
}
module.exports = AdminContract;
index.js
This file is main entry point all smart contract.
'use strict';
const CommonContract = require('./lib/CommonContract.js');
const AdminContract = require('./lib/AdminContract.js');
module.exports.contracts = [CommonContract, AdminContract];
admin.js
This is server side route file which invoke the getLastPatientId function which is in AdminContract.js smart contract file.
router.get('/getAllPatient', async (req, res) => {
try {
// load the network configuration
const ccpPath = path.resolve(__dirname, '..', '..', 'network-config', 'organizations', 'peerOrganizations', 'doctor.hospital_network.com', 'connection-doctor.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const identity = await wallet.get('appUser1');
if (!identity) {
console.log('An identity for the user "appUser" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'appUser1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('hospital');
// Get the contract from the network.
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('getLastPatientId');
const data = JSON.parse(result);
console.log(`Transaction has been evaluated, result is: ${result}`);
// Disconnect from the gateway.
await gateway.disconnect();
return data;
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
});
I found out that evalutateTransaction() invokes the function in CommonContract but was not able to invoke AdminContract functions. Please help me out. What is the error in my code ?
This is because you need to specify the contract name when calling transactions, except for the first contract which is treated as a default. For example, you should be able to successfully call initLedger, CommonContract:initLedger, and AdminContract:getLastPatientId but getLastPatientId will fail because there is no such transaction on the default contract.
You can see what transactions are available, and which contract is the default, by getting the metadata for the contract. You can get the metadata using the org.hyperledger.fabric:GetMetadata transaction, where org.hyperledger.fabric is the system contract and GetMetadata is the transaction. The ccmetadata utility will call that get metadata transaction if that helps.
You can also customise the contract names using a constructor. For example, to call an Admin:getLastPatientId transaction, add the following constructor:
class AdminContract extends Contract {
constructor() {
super('Admin');
}
//...
}
Note: I don't think it's related to your current problem but I'm not sure why you want to use inheritance in this case. It might cause other problems so I would stick to extending Contract as shown above.
First I was implementing multiple contracts.
By getting the metadata for the contracts there is one default contract.
{
CommonContract: {
name: 'CommonContract',
contractInstance: { name: 'CommonContract', default: true },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
AdminContract: {
name: 'AdminContract',
contractInstance: { name: 'AdminContract' },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
DoctorContract: {
name: 'DoctorContract',
contractInstance: { name: 'DoctorContract' },
transactions: [ [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
PatientContract: {
name: 'PatientContract',
contractInstance: { name: 'PatientContract' },
transactions: [ [Object], [Object] ],
info: { title: '', version: '' }
},
'org.hyperledger.fabric': {
name: 'org.hyperledger.fabric',
contractInstance: { name: 'org.hyperledger.fabric' },
transactions: [ [Object] ],
info: { title: '', version: '' }
}
}
Now in the admin.js file, looking at the line where we getting the contract
const contract = network.getContract('basic');
Here this statement was using default contract i.e. CommonContract, But I was implementing multiple contracts so I have to give the reference of that contract, so I make this change.
const contract = network.getContract('basic', 'AdminContract');
and now when I invoke the contract
const result = await contract.evaluateTransaction('getLastPatientId');
or
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('AdminContract:getLastPatientId');
It works...

cant update using updateExpression on Dynamodb using nodejs

Here is my data structure -
{
"pk": "USER-12560000000",
"sk": "USER",
"data": {
"name": "George", //I want to modify the value of this attribute
"updatedAt": Date.now() // I want to add this attribute to the data field
}
}
I have been trying this below updateExpression, but for some reason it doesn't even give a clue on whats wrong in cloudwatch. In the below code I am only trying to update the value of the displayName attribute, not sure on the code on how to add an extra attribute. The cloudwatch logs dont print the error reason as well. I have read the documentation on aws also a few other stackoveflow posts. I haven't been able to make this work.
Main Lambda Handler -
const updateUser = require('./user-queries/updateUser');
exports.handler = async (event) => {
var userId = event.arguments.userId;
var name = event.arguments.name;
var avatarUrl = event.arguments.avatarUrl;
console.log(event.info.fieldName);
switch(event.info.fieldName) {
case "updateUser":
return updateUser(userId, name, avatarUrl);
}
};
const AWS = require("aws-sdk")
AWS.config.update({ region: "ca-central-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()
async function updateUser(userId, name, avatarUrl) {
dynamoDB
.update({
TableName: "Bol-Table",
Key: {
pk: "USER-12560000000",
sk: "USER",
},
UpdateExpression: `set data = :data`,
ExpressionAttributeValues: {
":data": {
"name": "Test"
},
},
})
.promise()
.then(data => console.log(data.Attributes))
.catch(console.error)
}
module.exports = updateUser;
This is what gets printed out in cloudwatch along with some billing information
2021-10-09T16:25:15.527Z b1a1e4cb-6001-415c-82a3-cfdc90413e4e INFO USER-12560000000 Sam www.bol.com
Your UpdateExpression & ExpressionAttributeValues are wrong, they should be:
UpdateExpression: `set data.name = :x`
...
ExpressionAttributeValues: {
":x": "Test"
}
The Key object also takes in the keys as a string so your Key object needs to look like:
Key: {
"pk": "USER-12560000000",
"sk": "USER"
}
To update/insert multiple fields, the below will work:
UpdateExpression: `set data.name = :x, data.updatedAt = :y`
...
ExpressionAttributeValues: {
":x": "Test",
":y": Date.now()
}
Changing the dynamodb.update like below made it all work after all.
await dynamoDB.update(params).promise();
Here is the full code for anyone who comes across this issue.
const AWS = require("aws-sdk")
AWS.config.update({ region: "ca-central-1" })
const dynamoDB = new AWS.DynamoDB.DocumentClient()
async function updateUser(userId, name, avatarUrl) {
var params = {
TableName: "Bol-Table",
Key: {
"pk": userId,
"sk": 'USER',
},
UpdateExpression: 'SET details.displayName = :name, details.avatarUrl = :avatarUrl, details.updatedAt = :updateDate',
ExpressionAttributeValues: {
':name': name,
':avatarUrl':avatarUrl,
':updateDate': new Date().toISOString()
},
ReturnValues: 'ALL_NEW'
};
const Item = await dynamoDB.update(params).promise();
console.log(Item)
}
module.exports = updateUser;

Retrieving a StringSet value using DynamoDB.DocumentClient in Node

The DynamoDB.DocumentClient automatically marshals & unmarshals values between JavaScript types DynamoDB's more descriptive AttributeMap type. However, when working with an Item that has a StringSet attribute, it does not seem to do the conversion automatically.
When adding a StringSet attribute to the table using DocumentClient, I use the createSet(...) method to convert the array to a Set. When retrieving the value back, what is the inverse of createSet(...)? Is the best practice to just access the Set's .values directly? And if so, is that documented somewhere?
Here's sample code adding an Item with a StringSet attribute, then retrieving that item:
const docClient = new DocumentClient();
const TableName = "StringSets-Example";
const PK = "Names";
const Values = ["Peter", "Paul", "Mary"];
const putParams = {
TableName,
Item: {
PK,
names: docClient.createSet(Values)
}
}
await docClient.put(putParams).promise();
// ... some time later, I can retrieve the value with ...
const getParams = {
TableName,
Key: { PK }
}
const result = await docClient.get(getParams).promise();
The result.Item there is a Set object, whereas I would expect it to be the same array I passed into createSet(...).
If interested in seeing this live, this repo has a fully-functioning example. Clone it, npm install, and run index.js and you'll see something like:
$ ./index.js
Running On: darwin 19.6.0
Node version: v12.20.0
AWS SDK version: 2.799.0
-------------------------
Creating table "StringSets-Example"
Waiting for "StringSets-Example" status to be "ACTIVE"
Table status is: CREATING
Table status is: ACTIVE
Put String Set "["Peter, "Paul, "Mary"]" into "StringSets-Example" with key "Names" and attribute "names"
Retrieved Item with key "Names" from "StringSets-Example"
The raw Item: {
PK: 'Names',
names: Set {
wrapperName: 'Set',
values: [ 'Mary', 'Paul', 'Peter' ],
type: 'String'
}
}
The raw Item.names.values: [ 'Mary', 'Paul', 'Peter' ]
-------------------------
Done. To clean up, run:
./src/deleteTable.js
The best solution I have here is to avoid the DocumentClient and the createSet(...) method. Here's a sample using AWS SDK V3:
const key = { PK: `SampleNames`, SK: `SampleNames` };
const names = new Set([`Peter`, `Paul`, `Mary`]);
const item = { ...key, names };
const marshalledItem = marshall(item);
console.log(`Raw item: ${inspect(item)}`)
console.log(`Marshalled item to PUT: ${inspect(marshalledItem, { depth: 4 })}`)
const client = new DynamoDBClient({});
await client.send(new PutItemCommand({
TableName: tableName,
Item: marshalledItem,
}));
const { Item } = await client.send(new GetItemCommand({
TableName: tableName,
Key: marshall(key),
}));
console.log(`Returned item: ${inspect(Item, { depth: 4 })}`);
console.log(`Unmarshalled returned item: ${inspect(unmarshall(Item))}`);
The console output from there is:
Raw item: {
PK: 'SampleNames',
SK: 'SampleNames',
names: Set { 'Peter', 'Paul', 'Mary' }
}
Marshalled item to PUT: {
PK: { S: 'SampleNames' },
SK: { S: 'SampleNames' },
names: { SS: [ 'Peter', 'Paul', 'Mary' ] }
}
Returned item: {
PK: { S: 'SampleNames' },
SK: { S: 'SampleNames' },
names: { SS: [ 'Mary', 'Paul', 'Peter' ] }
}
Unmarshalled returned item: {
PK: 'SampleNames',
SK: 'SampleNames',
names: Set { 'Mary', 'Paul', 'Peter' }
}
... which makes a lot more sense to me. I expect using the marshall/unmarshall methods from AWS SDK V2's Converter module would work similarly.

Remove JSON Element if it doesn't contain a specific value

I'm trying to return only values where john is found from a DynamoDB database.
I'm able to return values where it contains name: john from a mapped list, however the problem am having is that it appears to also be returning other values as well.
Running select: 'count' returns 1 match which is correct but it doesn't return anything when used.
I'm assuming that count just returns a number and not a specific select where john is matched.
I'm writing this in NodeJS; am hoping someone can help me figure this out.
I know that the value I only want shown are json elements where name: john, anything else I want omitted from being shown.
Here's my result as of right now:
{
"Department": [
{
"employees": [
{
"name": "john"
},
{
"name": "sally"
}
]
}
],
"Count": 1
}
My code:
const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies
const dc = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context, callback) => {
// Construct the params for filtering data through dynamodb
const params = {
FilterExpression: "contains(#department, :employees)",
ExpressionAttributeNames: {
"#department": "employees",
},
ExpressionAttributeValues: {
":employees": {
"name":"john"
}
},
ProjectionExpression: "#department",
TableName: 'mytable',
//Select: 'COUNT'
};
const resultDC = await dc.scan(params).promise();
var items = resultDC.Items;
var count = resultDC.Count;
var returned_list = {
'Department' : items,
'Count' : count,
};
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(returned_list),
};
callback(null, response);
};
I suggest you to use a Local Secondary Index.
Take a look here.

How to query a table by using IN conditions in DynamoDB

Struggling to find an example of how to query a table to return rows with ids from a given list.
The query below throws on the inclusion of IN
var params = {
id: '7deb3df9-552b-47a4-aef3-ad601f141d50'
};
var p = {
TableName: 'players',
KeyConditionExpression: 'id IN (:id)',
ExpressionAttributeValues: buildQuery(params)
};
You can't use "IN" operator with KeyConditionExpression, please see details in this SO question
You may want to use batchGetItem instead of query, which is not so efficient though.
Here is how your params could look like:
var params = {
RequestItems: {
'players': {
Keys: [{
id: "7deb3df9-552b-47a4-aef3-ad601f141d50",
rangeKey: "<range key 1>" // <--- if your table has a range key, you must specify its value here
}, {
id: "<ANOTHER ID 2>",
rangeKey: "<range key 2>"
}, {
id: "<ANOTHER ID 3>",
rangeKey: "<range key 3>"
}]
}
}
};
dynamodbDoc.batchGet(params, function(err, data) {
});

Resources