Parameterize Query while using OFFSET clause - azure-cosmosdb

I've used following stored procedure and try to parameterize the query by passing the number parameter. However it gives me error while executing the stored procedure. Any insights is really helpful
function uspGetUsersByPage(number) {
//Set Environment
let context = getContext();
let coll = context.getCollection();
let link = coll.getSelfLink();
let response = context.getResponse();
let query = {
query: 'SELECT * FROM a WHERE a.DocumentType = "UserRole" and a.AuditFields.IsLatest = true and a.AuditFields.IsDeleted = false OFFSET #number LIMIT 20'
, parameters: [{ name: '#number', value: number }]
};
//Execute the query against the collection
let runquery = coll.queryDocuments(link, query, {}, callbackfn);
//Call function to throw an error(if any) or display the output
function callbackfn(err, queryoutput) {
if (err) {
throw err;
}
if (!queryoutput || !queryoutput.length) {
response.setBody(null);
}
else {
response.setBody(queryoutput);
}
};
//Display standard output if query doesnt get any results
if (!runquery) { throw Error('Unable to retrieve requested information'); }
};

Please see my simple test following your description.
Data:
Stored procedure:
function sample(prefix) {
var collection = getContext().getCollection();
var query = {query: "SELECT c.id,c.number FROM c offset #num limit 1", parameters:
[{name: "#num", value: prefix}]};
console.log(query);
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
query,
function (err, feed, options) {
if (err) throw err;
// Check the feed and if empty, set the body to 'no docs found', 
// else take 1st element from feed
if (!feed || !feed.length) {
var response = getContext().getResponse();
response.setBody('no docs found');
}
else {
var response = getContext().getResponse();
response.setBody(feed);
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
Input:
Output:

Related

DynamoDB table seed works in cli but not AWS-SDK

I have a table that has more than 25 items and wrote a basic script to break them into sub arrays of 25 items each then loops thru that collection of sub arrays to run a batch write item command in the AWS DynamoDB Client. The issue I am getting is a returned validation error. When I run the same seed file via the aws-cli it seeds the table perfectly. This makes me think it has something to do with my script. See anything I am missing? Thanks in advance!
var { DynamoDB } = require('aws-sdk');
var db = new DynamoDB.DocumentClient({
region: 'localhost',
endpoint: 'http://localhost:8000',
});
const allItems = require('./allItems.json');
const tableName = 'some-table-name';
console.log({ tableName, allItems });
var batches = [];
var currentBatch = [];
var count = 0;
for (let i = 0; i < allItems.length; i++) {
//push item to the current batch
count++;
currentBatch.push(allItems[i]);
if (count === 25) {
batches.push(currentBatch);
currentBatch = [];
}
}
//if there are still items left in the curr batch, add to the collection of batches
if (currentBatch.length > 0 && currentBatch.length !== 25) {
batches.push(currentBatch);
}
var completedRequests = 0;
var errors = false;
//request handler for DynamoDB
function requestHandler(err, data) {
console.log('In the request handler...');
return function (err, data) {
completedRequests++;
errors = errors ? true : err;
//log error
if (errors) {
console.error('Request caused a DB error.');
console.error('ERROR: ' + err);
console.error(JSON.stringify(err, null, 2));
} else {
var res = {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify(data),
isBase64Encoded: false,
};
console.log(`Success: returned ${data}`);
return res;
}
if (completedRequests == batches.length) {
return errors;
}
};
}
//Make request
var params;
for (let j = 0; j < batches.length; j++) {
//items go in params.RequestedItems.id array
//format for the items is {PutRequest : {Item: ITEM_OBJECT}}
params = '{"RequestItems": {"' + tableName + '": []}}';
params = JSON.parse(params);
params.RequestItems[tableName] = batches[j];
console.log('before db.batchWriteItem: ', params);
try {
//send to db
db.batchWrite(params, requestHandler(params));
} catch{
console.error(err)
}
}
Here is the formatted request object and the error:
before db.batchWriteItem:
{ RequestItems:
{ 'some-table-name': [ [Object], [Object], [Object], [Object] ] }
}
In the request handler...
Request caused a DB error.
ERROR: ValidationException: Invalid attribute value type
{
"message": "Invalid attribute value type",
"code": "ValidationException",
"time": "2020-08-04T10:51:13.751Z",
"requestId": "dd49628c-6ee9-4275-9349-6edca29636fd",
"statusCode": 400,
"retryable": false,
"retryDelay": 47.94198279972915
}
You are using the DocumentClient in the nodejs code. This will automatically convert the data format used by DynamoDB to a more easily consumable format.
e.g.
{
"id": {
"S": "A string value"
}
}
would become
{
"id": "A string value"
}
The CLI does not perform this data conversion.
You can use the regular DynamoDB client to not perform this conversion in Nodejs. e.g. const db = new Dynamodb()

How to dynamically update an attribute in a dynamodb item?

I created an item in dynamodb using Node js, the item has multiple attributes such as brand, category, discount, validity, etc. I am using uuid to generate ids for each item. Now let's say I want to update the validity attribute of the item, in which case I am currently sending the entire json object with the value of validity modified to the new value.
This is definitely not optimal, please help me find an optimal solution.
const params = {
TableName: process.env.PRODUCT_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeNames: {
'#discount': 'discount',
},
ExpressionAttributeValues: {
':brand': data.brand,
':category': data.category,
':discount': data.discount,
':denominations': data.denominations,
":validity": data.validity,
":redemption": data.redemption
},
UpdateExpression: 'SET #discount = :discount, denominations = :denominations, brand = :brand, category = :category, validity = :validity, redemption = :redemption',
ReturnValues: 'ALL_NEW',
};
I want to send just the attribute I want to update with the new value, if I want to change the validity from 6 months to 8 months, I should just send something like:
{
"validity": "8 months"
}
And it should update the validity attribute of the item.
Same should apply to any other attribute of the item.
'use strict';
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.update = (event, context, callback) => {
const data = JSON.parse(event.body);
let attr = {};
let nameobj = {};
let exp = 'SET #';
let arr = Object.keys(data);
let attrname = {};
arr.map((key) => {attr[`:${key}`]=data[key]});
arr.map((key) => {
exp += `${key} = :${key}, `
});
arr.map((key) => {nameobj[`#${key}`]=data[key]});
attrname = {
[Object.keys(nameobj)[0]] : nameobj[Object.keys(nameobj)[0]]
}
const params = {
TableName: process.env.PRODUCT_TABLE,
Key: {
id: event.pathParameters.id,
},
ExpressionAttributeNames: attrname,
ExpressionAttributeValues: attr,
UpdateExpression: exp,
ReturnValues: 'ALL_NEW',
};
// update the todo in the database
dynamoDb.update(params, (error, result) => {
// handle potential errors
if (error) {
console.error(error);
callback(null, {
statusCode: error.statusCode || 501,
headers: { 'Content-Type': 'text/plain' },
body: 'Couldn\'t update the card',
});
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(result.Attributes),
};
callback(null, response);
});
};
Contrary to others comments, this is very possible, use the UpdateItem action.
Language agnostic API docs
JavaScript specific API docs
If you want to dynamically create the query, try something like this:
const generateUpdateQuery = (fields) => {
let exp = {
UpdateExpression: 'set',
ExpressionAttributeNames: {},
ExpressionAttributeValues: {}
}
Object.entries(fields).forEach(([key, item]) => {
exp.UpdateExpression += ` #${key} = :${key},`;
exp.ExpressionAttributeNames[`#${key}`] = key;
exp.ExpressionAttributeValues[`:${key}`] = item
})
exp.UpdateExpression = exp.UpdateExpression.slice(0, -1);
return exp
}
let data = {
'field' : { 'subfield': 123 },
'other': '456'
}
let expression = generateUpdateQuery(data)
let params = {
// Key, Table, etc..
...expression
}
console.log(params)
Output:
{
UpdateExpression: 'set #field = :field, #other = :other',
ExpressionAttributeNames: {
'#field': 'field',
'#other': 'other'
},
ExpressionAttributeValues: {
':field': {
'subfield': 123
},
':other': '456'
}
}
Using Javascript SDK V3:
Import from the right package:
import { DynamoDBClient PutItemCommandInput, UpdateItemCommandInput, UpdateItemCommand } from '#aws-sdk/client-dynamodb';
Function to dynamically do partial updates to the item:
(the code below is typescript can be easily converted to Javascript, just remove the types!)
function updateItem(id: string, item: any) {
const dbClient = new DynamoDBClient({region: 'your-region-here });
let exp = 'set ';
let attNames: any = { };
let attVal: any = { };
for(const attribute in item) {
const valKey = `:${attribute}`;
attNames[`#${attribute}`] = attribute;
exp += `#${attribute} = ${valKey}, `;
const val = item[attribute];
attVal[valKey] = { [getDynamoType(val)]: val };
}
exp = exp.substring(0, exp.length - 2);
const params: UpdateItemCommandInput = {
TableName: 'your-table-name-here',
Key: { id: { S: id } },
UpdateExpression: exp,
ExpressionAttributeValues: attVal,
ExpressionAttributeNames: attNames,
ReturnValues: 'ALL_NEW',
};
try {
console.debug('writing to db: ', params);
const command = new UpdateItemCommand(params);
const res = await dbClient.send(command);
console.debug('db res: ', res);
return true;
} catch (err) {
console.error('error writing to dynamoDB: ', err);
return false;
}
}
And to use it (we can do partial updates as well):
updateItem('some-unique-id', { name: 'some-attributes' });
What i did is create a helper class.
Here is a simple function : Add all the attribute and values that goes into, if the value is null or undefined it won't be in the expression.
I recommande to create a helper class with typescript and add more functions and other stuff like generator of expressionAttributeValues , expressionAttributeNames ... , Hope this help.
function updateExpression(attributes, values) {
const expression = attributes.reduce((res, attribute, index) => {
if (values[index]) {
res += ` #${attribute}=:${attribute},`;
}
return res;
}, "SET ");
return expression.slice(0, expression.length - 1)
}
console.log(
updateExpression(["id", "age", "power"], ["e8a8da9a-fab0-55ba-bae3-6392e1ebf624", 28, undefined])
);
You can use code and generate the params object based on the object you provide. It's just a JavaScript object, you walk through the items so that the update expression only contains the fields you have provided.
This is not really a DynamoDB question in that this is more a general JS coding question.
You can use UpdateItem; to familiarize yourself with DynamoDb queries I would suggest you DynamoDb NoSQL workbench:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html
It can generate snippets for you based on your queries.
DynamoDb NoSQL workbench screenshot query

how to insert data into SQLite using ionic

I am new to ionic.I want to add data into SQLite which is coming from remote server. I have successfully populated data into list.so how can i store this data into sqlite. here is my code. how do i pass this data to query.I am unable to do this.
service.js
angular.module('starter.service',[]).
factory('userServices',['$http',function($http){
var users = [];
return {
get: function(){
return $http.get("http://xxxxxxxxx-info").then(function(response){
users = response.data;
return users;
});
},
remove:function(content){
users.splice(users.indexOf(content),1);
},
getUser:function(chatId)
{
for(var i=0; i<users.length;i++){
if(users[i].content_id === parseInt(chatId)){
return users[i];
}
}
return null;
}
}
}]);
controller.js
angular.module('shoppingPad.controller', [])
.controller('ChatCtrl', function ($scope, userServices, $ionicModal, $cordovaSQLite) {
console.log('inside controller');
userServices.get().then(function (users) {
//users is an array of user objects
$scope.contents = users;
console.log($scope.contents);
var query = "INSERT INTO content (content_id, display_name) VALUES (?,?)";
$cordovaSQLite.execute(db, query, [users.content_id, users.display_name]).then(function (res) {
alert(res);
alert('Inserted');
}, function (e) {
alert('Error:' + e.message);
});
});
Where did you define db? It's necessary to wait until device is ready.
$ionicPlatform.ready(function () {
var db = $cordovaSQLite.openDB({ name: "my.db" });
// just first time you need to define content table
$cordovaSQLite.execute(db,"CREATE TABLE content (content_id integer, display_name text)");
userServices.get().then(function (users) {
//users is an array of user objects
$scope.contents = users;
console.log($scope.contents);
var query = "INSERT INTO content (content_id, display_name) VALUES (?,?)";
$cordovaSQLite.execute(db, query, [users.content_id, users.display_name]).then(function (res) {
alert(res);
alert('Inserted');
}, function (e) {
alert('Error:' + e.message);
});
});
});
Are you sure, that your object users look like
{
"content_id":12,
"display_name":"hello world"
}
and not like
[
{
"content_id":12,
"display_name":"hello world"
},
{
"content_id":13,
"display_name":"stackoverflow"
},
...
]
I just ask, because users sounds like more than one entry.

Document DB 5 secs stored procedure execution limit

I have a stored procedure which return 4000 documents, due to 5 sec execution limit my sproc is not retuning any data. I tried to handled collection accepted value but it is not working as expected.
I tried to set response to "return" in case of 5 sec limit hit, sproc is not setting response.
function getRequirementNodes()
{
var context = getContext();
var response = context.getResponse();
var collection = context.getCollection();
var collectionLink = collection.getSelfLink();
var nodesBatch = [];
var continueToken= true;
var query = { query: 'SELECT * from root '};
getNodes(null)
function getNodes(continuation)
{
var requestOptions = {continuation: continuation};
var accepted = collection.queryDocuments(collectionLink, query, requestOptions,
function(err, documentsRead, responseOptions)
{
if (documentsRead.length > 0)
{
nodesBatch = nodesBatch.concat(documentsRead);
}
else if (responseOptions.continuation)
{
continueToken = responseOptions.continuation
nodesBatch = nodesBatch.concat(documentsRead);
getNodes(responseOptions.continuation);
}
else
{
continueToken= false;
response.setBody(nodesBatch);
}
});
if (!accepted)
{
response.setBody("return");
}
}
}
The script is returning a empty response, because the blocks containing response.setBody() are never called.
I'll explain. Let's break this section of queryDocuments callback down:
if (documentsRead.length > 0) {
nodesBatch = nodesBatch.concat(documentsRead);
} else if (responseOptions.continuation) {
continueToken = responseOptions.continuation
nodesBatch = nodesBatch.concat(documentsRead);
getNodes(responseOptions.continuation);
} else {
continueToken = false;
response.setBody(nodesBatch);
}
Note that if the query has results inside the first page (which it most likely will)... The script will append the query results on to nodesBatch:
if (documentsRead.length > 0) {
nodesBatch = nodesBatch.concat(documentsRead);
}
The script will then complete. The response body is unset (empty), and the script does not issue a follow up query if there is a continuation token.
Assuming the collection isn't empty, then this probably the behavior you are experiencing.
Note: If you are querying a large dataset, it's possible to hit the response size limit (1 MB).
I've re-wrote the script to fix the issue above, and have included a snippet to illustrate how to handle the response size limit:
function getRequirementNodes(continuationToken) {
var context = getContext();
var response = context.getResponse();
var collection = context.getCollection();
var collectionLink = collection.getSelfLink();
var nodesBatch = [];
var lastContinuationToken;
var responseSize = 0;
var query = {
query: 'SELECT * FROM root'
};
getNodes(continuationToken);
function getNodes(continuationToken) {
// Tune the pageSize to fit your dataset.
var requestOptions = {
continuation: continuationToken,
pageSize: 1
};
var accepted = collection.queryDocuments(collectionLink, query, requestOptions,
function(err, documentsRead, responseOptions) {
// The size of the current query response page.
var queryPageSize = JSON.stringify(documentsRead).length;
// DocumentDB has a response size limit of 1 MB.
if (responseSize + queryPageSize < 1024 * 1024) {
// Append query results to nodesBatch.
nodesBatch = nodesBatch.concat(documentsRead);
// Keep track of the response size.
responseSize += queryPageSize;
if (responseOptions.continuation) {
// If there is a continuation token... Run the query again to get the next page of results
lastContinuationToken = responseOptions.continuation;
getNodes(responseOptions.continuation);
} else {
// If there is no continutation token, we are done. Return the response.
response.setBody({
"message": "Query completed succesfully.",
"queryResponse": nodesBatch
});
}
} else {
// If the response size limit reached; run the script again with the lastContinuationToken as a script parameter.
response.setBody({
"message": "Response size limit reached.",
"lastContinuationToken": lastContinuationToken,
"queryResponse": nodesBatch
});
}
});
if (!accepted) {
// If the execution limit reached; run the script again with the lastContinuationToken as a script parameter.
response.setBody({
"message": "Execution limit reached.",
"lastContinuationToken": lastContinuationToken,
"queryResponse": nodesBatch
});
}
}
}

JavaScript - passing variable from one function to another is undefined

in my example I'm trying to pass a variable (var ttoken) from one function to another and save it to the SQLite. The coding environment is Phonegap (for android). Here is the procedure:
var ttoken; // global var declaration
function handleLogin() {
var form = $("#loginForm");
var u = $("#username", form).val();
var p = $("#password", form).val();
if(u!= '' && p!= '') {
$.post("http://localhost/login.php", {username:u, password:p}, function(data){
if(data!='') {
$.mobile.changePage("change_page.html");
ttoken = data.token;
} else {
navigator.notification.alert("Error try again", function() {});
}
}, "json");
} else {
navigator.notification.alert("Error, fields are emty", function() {});
}
return {tkn:ttoken}; // putting into array
openDB();
populateDB();
}
var db;
function openDB(){ // create database
// 'Kurskoffer_DB' vol. 300 Kb
db = window.openDatabase("Sample_DB", "1.0", "Samole DB", 300000);
db.transaction(populateDB, errorCB, successCB);
}
function populateDB(tx){ // create 'settings' table
var tooken = handleLogin(); // accessing the variable ttoken
tx.executeSql('CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY, token TEXT NOT NULL, sound TEXT NOT NULL, vibra TEXT NOT NULL)');
tx.executeSql('INSERT INTO settings(id, token, sound, vibra) VALUES (1, "'+tooken.tkn+'", "on", "on")');
}
Seems everything according variable passing rule is ok, but the insert result in the table for field token is undefined. Have anyone idea why this is happening? Thanks.
The $.post function is asynchronous, which means handleLogin will return before the post callback has fired, and ttoken remains undefined in the returned object.
Set up handleLogin to accept a callback, which will fire when the post has returned and ttoken has been populated.
Something like:
function handleLogin(callback) {
var form = $("#loginForm");
var u = $("#username", form).val();
var p = $("#password", form).val();
if(u!= '' && p!= '') {
$.post("http://localhost/login.php", {username:u, password:p}, function(data){
if(data!='') {
$.mobile.changePage("change_page.html");
ttoken = data.token;
if (callback) callback();
} else {
navigator.notification.alert("Error try again", function() {});
}
}, "json");
} else {
navigator.notification.alert("Error, fields are emty", function() {});
}
}
function populateDB(tx){ // create 'settings' table
handleLogin(function() {
tx.executeSql('CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY, token TEXT NOT NULL, sound TEXT NOT NULL, vibra TEXT NOT NULL)');
tx.executeSql('INSERT INTO settings(id, token, sound, vibra) VALUES (1, "'+ttoken+'", "on", "on")');
});
}
Since ttoken is defined globally you don't have to worry about passing it around as an argument.

Resources