I have a problem with an ionic3 app I'm been developing.
With my app I conect to a JSON server and download a lot of rows, when I have all rows with a for, in each row call to funcion to insert data.
but I think, my for is faster than insert funcion and show finalice alert before end. this is my code:
private downloadRows()
{
this.platform.ready().then(()=>{
this.translateService.get('ACTUALIZANDOBARRANCOS').subscribe(
value => {
let loadingContent = value;
let loader = this.loadingController.create({
content: loadingContent,
spinner: "bubbles"
});
this.servicioDownloads.downloadAllRows().then(rows=> {
this.datosrows= rows;
loader.present();
for (var i = 0; i < this.datosrows.length; i++)
{
var datos = this.datosrows[i];
// we are going to insert rows
this.servicioDataBase.insertRow
( datos.indice,
datos.row1,
datos.row2,
datos.row3,
datos.row4,
datos.row5,
datos.row6,
datos.row7,
datos.row8,
datos.row9,
datos.row10,
//...
datos.row30
).catch(()=>{
console.log("da error");
});
}
loader.dismiss();
this.translateService.get('FINALIZADO').subscribe(
Titulo =>{
let titulo = Titulo;
this.translateService.get('BARRANCOSACTUALIZADOS').subscribe(
Descripcion =>{
let descripcion = Descripcion;
let alerta = this.alertCtrl.create({
title: titulo,
subTitle: descripcion,
buttons: ['OK']
})
alerta.present();
}
);
}
);
}).catch(error=>{
loader.dismiss();
this.translateService.get('ERROR').subscribe(
Titulo =>{
let titulo = Titulo;
this.translateService.get('ERRORDESCARGABARRANCOS').subscribe(
Descripcion =>{
let descripcion = Descripcion;
let alerta = this.alertCtrl.create({
title: titulo,
subTitle: descripcion,
buttons: ['OK']
})
alerta.present();
}
);
}
);
})
});
})
}
//This is insert services
public insertRow( indice: any, row1: any, row2: any, row3: any, row4: any,
row5: any, row6: any, row7: any, row8: any, row9: any, row10: any,
row30: any)
{
let sql = "INSERT INTO TableRows (id,Nombre,Rio,Pais,Comunidad,
Zona,Localidad,Interes,Caracter,Cascada_max,Cuerda_max,Desnivel,
Longitud, Tiempo_aprox,Tiempo_descenso,Tiempo_retorno,Observaciones,
Descripcion_barranco,Periodo_optimo,Caudal,Caudal_filtro,Aproximacion,
Retorno,Loc_entrada_barranco,Loc_salida_barranco,Loc_entrada_parking,
Loc_salida_parking,Autor,Actualizacion,Idioma,Visible)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
console.log(sql);
return this.isReady()
.then(()=>{
return this.database.executeSql(sql,
[indice,BARRANCO, RIO, PAIS,COMUNIDAD,
ZONA,LOCALIDAD,INTERES,CARACTER, CASCADA_MAX,
CUERDA_MAX,DESNIVEL,LONGITUD,TIEMPO_APROX,
TIEMPO_DESCENSO,TIEMPO_RETORNO, OBSERVACIONES,
DESCRIPCION_BARRANCO,PERIODO_OPTIMO,CAUDAL,
CAUDAL_FILTRO,APROXIMACION, RETORNO,
LOC_ENTRADA_BARRANCO,LOC_SALIDA_BARRANCO,
LOC_ENTRADA_PARKING, LOC_SALIDA_PARKING,
AUTOR,actualizaciĆ³n,idioma, visible]);
}).then((data)=>{
console.log("El insert devuelve esto " + JSON.stringify(data));
})
}
Somebody knows how can I do on time, I mean insert when for ends?
thanks a lot!
just use async/await in your function, it will allow you call async functions in sequence manner (since Typescript 2.1 you are able to downlevel async/await to ES3/ES5). So solution in your example could look like this:
private downloadRows()
{
this.platform.ready().then(()=>{
this.translateService.get('ACTUALIZANDOBARRANCOS').subscribe(
async value => {
let loadingContent = value;
let loader = this.loadingController.create({
content: loadingContent,
spinner: "bubbles"
});
let rows = await this.servicioDownloads.downloadAllRows();
loader.present();
for (let datos of rows) {
// we are going to insert rows
try {
await this.servicioDataBase.insertRow
( datos.indice,
datos.row1,
datos.row2,
datos.row3,
datos.row4,
datos.row5,
datos.row6,
datos.row7,
datos.row8,
datos.row9,
datos.row10,
//...
datos.row30
);
}
catch(error) {
console.log(error);
}
}
loader.dismiss();
this.translateService.get('FINALIZADO').subscribe(
Titulo =>{
let titulo = Titulo;
this.translateService.get('BARRANCOSACTUALIZADOS').subscribe(
Descripcion =>{
let descripcion = Descripcion;
let alerta = this.alertCtrl.create({
title: titulo,
subTitle: descripcion,
buttons: ['OK']
})
alerta.present();
}
);
}
);
}).catch(error=>{
loader.dismiss();
this.translateService.get('ERROR').subscribe(
Titulo =>{
let titulo = Titulo;
this.translateService.get('ERRORDESCARGABARRANCOS').subscribe(
Descripcion =>{
let descripcion = Descripcion;
let alerta = this.alertCtrl.create({
title: titulo,
subTitle: descripcion,
buttons: ['OK']
})
alerta.present();
}
);
}
);
})
});
})
await in for cycle prevents for cycle to finish before insert is actually done.
SQLite Porter - This Cordova/Phonegap plugin can be used to import/export to/from a SQLite database using either SQL or JSON.
Installation:
$ ionic cordova plugin add uk.co.workingedge.cordova.plugin.sqliteporter
$ npm install --save #ionic-native/sqlite-porter
Usage:
import { SQLitePorter } from '#ionic-native/sqlite-porter';
constructor(private sqlitePorter: SQLitePorter) { }
...
let db = window.openDatabase('Test', '1.0', 'TestDB', 1 * 1024);
// or we can use SQLite plugin
// we will assume that we injected SQLite into this component as sqlite
this.sqlite.create({
name: 'data.db',
location: 'default'
})
.then((db: any) => {
let dbInstance = db._objectInstance;
// we can pass db._objectInstance as the database option in all SQLitePorter methods
});
let sql = 'CREATE TABLE Artist ([Id] PRIMARY KEY, [Title]);' +
'INSERT INTO Artist(Id,Title) VALUES ("1","Fred");';
this.sqlitePorter.importSqlToDb(db, sql)
.then(() => console.log('Imported'))
.catch(e => console.error(e));
You can try using cordova-sqlite-porter. Pass it your inserts as
a JSON structure using importJsonToDb() and it will optimise the
insertion into the SQLite DB.
The example project illustrates insertion of 15,000+ records. On
a Samsung Galaxy S4, performing this using single SQL insert
statements takes around 5 minutes/300 seconds, but the optimised JSON
equivalent (using UNION SELECT - see here for info) takes around
3 seconds on the same device - 100X faster. - source
Related
I'm trying to do a pagination where the user can see each button's page number in the UI. I'm using Firestore and Buefy for this project.
My problem is that Firestore is returning wrong queries for this case. Sometimes (depending the page that the users clicks on) It works but sometimes don't (It returns the same data of the before page button).
It's really messy I don't understand what's going on. I'll show you the code:
Vue component: (pay attention on the onPageChange method)
<template>
<div>
<b-table
:data="displayData"
:columns="table.columns"
hoverable
scrollable
:loading="isLoading"
paginated
backend-pagination
:total="table.total"
:per-page="table.perPage"
#page-change="onPageChange">
</b-table>
</div>
</template>
<script>
import { fetchBarriosWithLimit, getTotalDocumentBarrios, nextBarrios } from '../../../../firebase/firestore/Barrios/index.js'
import moment from 'moment'
const BARRIOS_PER_PAGE = 5
export default {
data() {
return {
table: {
data: [],
columns: [
{
field: 'name',
label: 'Nombre'
},
{
field: 'dateAddedFormatted',
label: 'Fecha aƱadido'
},
{
field: 'totalStreets',
label: 'Total de calles'
}
],
perPage: BARRIOS_PER_PAGE,
total: 0
},
isLoading: false,
lastPageChange: 1
}
},
methods: {
onPageChange(pageNumber) {
// This is important. this method gets fired each time a user clicks a new page. I page number that the user clicks.
this.isLoading = true
if(pageNumber === 1) {
console.log('show first 5...')
return;
}
const totalPages = Math.ceil(this.table.total / this.table.perPage)
if(pageNumber === totalPages) {
console.log('show last 5...')
return;
}
/* Here a calculate the next starting point */
const startAfter = (pageNumber - 1) * this.table.perPage
nextBarrios(this.table.perPage, startAfter)
.then((querySnap) => {
this.table.data = []
this.buildBarrios(querySnap)
console.log('Start after: ', startAfter)
})
.catch((err) => {
console.err(err)
})
.finally(() => {
this.isLoading = false
})
},
buildBarrios(querySnap) {
querySnap.docs.forEach((docSnap) => {
this.table.data.push({
id: docSnap.id,
...docSnap.data(),
docSnapshot: docSnap
})
});
}
},
computed: {
displayData() {
let data = []
this.table.data.map((barrioBuieldedObj) => {
barrioBuieldedObj.dateAddedFormatted = moment(Number(barrioBuieldedObj.dateAdded)).format("DD/MM/YYYY")
barrioBuieldedObj.totalStreets ? true : barrioBuieldedObj.totalStreets = 0;
data.push(barrioBuieldedObj)
});
return data;
}
},
mounted() {
// obtener primer paginacion y total de documentos.
this.isLoading = true
getTotalDocumentBarrios()
.then((docSnap) => {
if(!docSnap.exists || !docSnap.data().totalBarrios) {
// mostrar mensaje que no hay barrios...
console.log('No hay barrios agregados...')
this.table.total = 0
return;
}
const totalBarrios = docSnap.data().totalBarrios
this.table.total = totalBarrios
if(totalBarrios <= BARRIOS_PER_PAGE) {
return fetchBarriosWithLimit(totalBarrios)
} else {
return fetchBarriosWithLimit(BARRIOS_PER_PAGE)
}
})
.then((querySnap) => {
if(querySnap.empty) {
// ningun doc. mostrar mensaje q no hay barrios agregados...
return;
}
this.buildBarrios(querySnap)
})
.catch((err) => {
console.error(err)
})
.finally(() => {
this.isLoading = false
})
}
}
</script>
<style lang="scss" scoped>
</style>
The nextBarrios function:
function nextBarrios(limitNum, startAtNum) {
const query = db.collection('Barrios')
.orderBy('dateAdded')
.startAfter(startAtNum)
.limit(limitNum)
return query.get()
}
db is the result object of calling firebase.firestore(). Can I tell a query to start at a certain number where number is the index position of the document within a collection? If not, How could I approach this problem?
Thank you!
Firestore doesn't support offset or index based pagination. It's also not possible to tell how many documents the entire query would return without actually reading them all. So, unfortunately, what you're trying to do isn't possible with Firestore.
It seems also that you're misunderstanding how the pagination APIs actually work. startAfter doesn't take an index - it takes either a DocumentSnapshot of the last document in the prior page, or a value of the ordered field that you used to sort the query, again, the last value you saw in the prior page. You are basically going to use the API to tell it where to start in the next page of results based on what you found in the last page. That's what the documentation means when it says you are working with a "query cursor".
i'm trying to add persistent attributes to my lambda function.
i created a dynamoDB table and added it to the triggers of my lambda function.
i copied a sample code from github, but when i try to launch the skill i get an error. The console log shows:
{
"errorMessage": "Could not read item (amzn1.ask.account.AGIIYNRXWDLBD6XEPW72QS2BHGXNP7NWYBEWSH2XLSXZP64X3NCYEMVK233VFDWH77ZB6DAK6YJ53SZLNUFVQ56CYOVCILS7QFZI4CIRDWC3PAHS4QG27YUY5PTT6QEIK46YFNTJT54YAKNGOWV2UO66XZACFDQ5SEXKJYOBNFNIZNUXKNTIAAYZG4R5ZU4FMLPDZZN64KLINNA) from table (Spiele): The provided key element does not match the schema",
"errorType": "AskSdk.DynamoDbPersistenceAdapter Error",
"stackTrace": [
"Object.createAskSdkError (/var/task/node_modules/ask-sdk-dynamodb-persistence-adapter/lib/utils/AskSdkUtils.js:22:17)",
"DynamoDbPersistenceAdapter.<anonymous> (/var/task/node_modules/ask-sdk-dynamodb-persistence-adapter/lib/attributes/persistence/DynamoDbPersistenceAdapter.js:123:49)",
"step (/var/task/node_modules/ask-sdk-dynamodb-persistence-adapter/lib/attributes/persistence/DynamoDbPersistenceAdapter.js:44:23)",
"Object.throw (/var/task/node_modules/ask-sdk-dynamodb-persistence-adapter/lib/attributes/persistence/DynamoDbPersistenceAdapter.js:25:53)",
"rejected (/var/task/node_modules/ask-sdk-dynamodb-persistence-adapter/lib/attributes/persistence/DynamoDbPersistenceAdapter.js:17:65)",
"<anonymous>",
"process._tickDomainCallback (internal/process/next_tick.js:228:7)"
]
}
the table contains a primary key "name" and sort key "UserId". is that wrong?
here is my index.js:
const Alexa = require('ask-sdk');
// Define the skill features
let skill;
/**
* If this is the first start of the skill, grab the user's data from Dynamo and
* set the session attributes to the persistent data.
*/
const GetUserDataInterceptor = {
process(handlerInput) {
let attributes = handlerInput.attributesManager.getSessionAttributes();
if (handlerInput.requestEnvelope.request.type === 'LaunchRequest' && !attributes['isInitialized']) {
return new Promise((resolve, reject) => {
handlerInput.attributesManager.getPersistentAttributes()
.then((attributes) => {
attributes['isInitialized'] = true;
saveUser(handlerInput, attributes, 'session');
resolve();
})
.catch((error) => {
reject(error);
})
});
}
}
};
function saveUser(handlerInput, attributes, mode) {
if(mode === 'session'){
handlerInput.attributesManager.setSessionAttributes(attributes);
} else if(mode === 'persistent') {
console.info("Saving to Dynamo: ",attributes);
return new Promise((resolve, reject) => {
handlerInput.attributesManager.getPersistentAttributes()
.then((persistent) => {
delete attributes['isInitialized'];
handlerInput.attributesManager.setPersistentAttributes(attributes);
resolve(handlerInput.attributesManager.savePersistentAttributes());
})
.catch((error) => {
reject(error);
});
});
}
}
const LaunchHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
console.info("LaunchRequest");
let attributes = handlerInput.attributesManager.getSessionAttributes();
console.info("Test the load: " + attributes['isInitialized']);
attributes['FOO'] = "BAR";
saveUser(handlerInput, attributes, 'persistent');
return handlerInput.responseBuilder
.speak('Hello')
.reprompt('Hello')
.getResponse();
}
}
exports.handler = Alexa.SkillBuilders.standard()
.addRequestHandlers(
LaunchHandler
)
.addRequestInterceptors(GetUserDataInterceptor)
.withTableName('Spiele')
.withAutoCreateTable(true)
.withDynamoDbClient()
.lambda();
can anyone tell me what i'm doing wrong?
please confirm the partition key is 'userId' not 'UserId' (notice the uppercase U).
Also I would suggest using 'this' object.
Let me know if that helps.
Cheers
Below code is for python lambda function
from ask_sdk_core.skill_builder import CustomSkillBuilder
from ask_sdk_dynamodb.adapter import DynamoDbAdapter
sb = SkillBuilder()
sb = CustomSkillBuilder(persistence_adapter = dynamodb_adapter)
I'm new to Vuejs. I want to have a form using which you can add products. The product image goes to firebase storage but how do I associate that image with the exact product in the database?
I've already set up my form, and created two methods. saveProduct() to save the products to the database and onFilePicked() to listen for changes in the input field and target the image and upload that to storage.
import { fb, db } from '../firebaseinit'
export default {
name: 'addProduct',
data () {
return {
product_id: null,
name: null,
desc: null,
category: null,
brand: null,
image: null,
}
},
methods: {
saveProduct () {
db.collection('products').add({
product_id: this.product_id,
name: this.name,
desc: this.desc,
category: this.category,
brand: this.brand
})
.then(docRef => {
this.$router.push('/fsbo/produkten')
})
},
onFilePicked (event) {
let imageFile = event.target.files[0]
let storageRef = fb.storage().ref('products/' + imageFile.name)
storageRef.put(imageFile)
}
}
}
what about this, you can use the filename, your images are going to be served as somefireurl.com/{your_file_name} on your product collection you can have an image prop with the imageFile.name.
methods: {
saveProduct (image = null) {
let productRef = db.collection('products').doc(this.product_id)
const payload = {
product_id: this.product_id,
name: this.name,
desc: this.desc,
category: this.category,
brand: this.brand
}
if (image) payload['image'] = image
return productRef
.set(payload, {merge: true})
.then(docRef => {
this.$router.push('/fsbo/produkten')
})
},
onFilePicked (event) {
let imageFile = event.target.files[0]
let storageRef = fb.storage().ref('products/' + imageFile.name)
storageRef.put(imageFile)
return this.saveProduct(imageFile.name)
}
}
That should be enough to get you started, maybe you want to try a different combination, or maybe you dont want to call saveProduct the way I set it, it's up to your use case but the idea is the same. Hope this can help you
I fixed it myself. Here's my solution. I don't know if it's technically correct but it works for my use case.
methods: {
saveProduct () {
let imageFile
let imageFileName
let ext
let imageUrl
let key
let task
db.collection('products').add({
product_id: this.product_id,
name: this.name,
desc: this.desc,
category: this.category,
brand: this.brand
})
.then(docRef => {
key = docRef.id
this.$router.push('/fsbo/produkten')
return key
})
.then(key => {
if(this.image !== null) {
this.onFilePicked
imageFile = this.image
imageFileName = imageFile.name
ext = imageFileName.slice(imageFileName.lastIndexOf('.'))
}
let storageRef = fb.storage().ref('products/' + key + '.' + ext)
let uploadTask = storageRef.put(imageFile)
uploadTask.on('state_changed', (snapshot) => {}, (error) => {
// Handle unsuccessful uploads
}, () => {
uploadTask.snapshot.ref.getDownloadURL().then( (downloadURL) => {
db.collection('products').doc(key).update({ imageUrl: downloadURL})
});
});
})
},
onFilePicked (event) {
return this.image = event.target.files[0]
}
}
In childSnapshot.val().k I have this with cloud function:
{ '-LdmZIlKZh3O9cR8MOBU':
{ id: 'ceccomcpmoepincin3ipwc',
k: 'test',
p: 'somepath',
t: 1556700282278,
u: 'username' },
'-Llkocp3ojmrpemcpo3mc':
{ id: '[epc[3pc[3m,',
k: 'test2',
p: 'somepath2',
t: 1556700292290,
u: 'username2' }
}
I need each path value so I can delete that file from storage. How to access this value?
My cloud function for refreshing states, removing and deleting files from storage:
var db = admin.database();
var ref = db.ref('someref');
ref.once("value").then((snapshot) => {
var updates = {};
var patObject = {
fid: null,
ft: null,
ftr: null,
fu: null,
id: null,
lid: null,
lt: null,
ltr: null,
lu: null,
t: null,
tr: null,
v: null,
g: null,
l: null,
k: null
};
snapshot.forEach((childSnapshot) => {
if(childSnapshot.numChildren() >= 14){
var t = childSnapshot.val().t;
if((t===1 || t===5) && childSnapshot.val().tr > 0) {
if(childSnapshot.val().tr - 12 > 0){
updates[childSnapshot.key + '/tr'] = childSnapshot.val().tr - 12;
if(childSnapshot.val().k !== ""){
console.log('path: ', childSnapshot.val().k);
childSnapshot.val().k.snapshot.forEach(kpath => {
console.log('path: ', "path");
});
}
} else {
updates[childSnapshot.key] = patObject;
}
}
if(childSnapshot.val().tr<=0){
updates[childSnapshot.key] = patObject;
}
} else {
updates[childSnapshot.key] = patObject;
}
});
ref.update(updates);
res.send("");
return "";
}).catch(reason => {
res.send(reason);
})
return "";
If you want to delete all the files corresponding to the values of the ps, you need to use Promise.all() to execute in parallel the asynchronous deletion tasks (Since the delete() method returns a Promise). You need to iterate over the object that contains the p paths.
It is not easy to understand your code, so you'll find below the part corresponding to the above explanations. It's up to you to integrate it in your code!
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const defaultStorage = admin.storage(); //Note this line
//.....
exports.date = functions.https.onRequest((req, res) => { //I understand that you use an HTTP Cloud Function
//....
.then(...
// Somehow you get the object you mention in your question, through childSnapshot.val().k
const kObject = childSnapshot.val().k;
const bucket = defaultStorage.bucket(yourFileBucket);
const promises = [];
Object.keys(kObject).forEach(
//The values of the path p are obtained via kObject[key].p
//Based on that we push the Promise returned by delete() to the promises array
promises.push(bucket.file(kObject[key].p).delete());
);
return Promise.all(promises)
.then(results => {
//Here all the Promises that were in the promises array are resolved, which means that all the files are deleted
res.send({result: results.length + ' files(s) deleted'});
})
.catch(error => {
res.status(500).send(error);
});
});
Watch may be interested by watching the following official Firebase video by Doug Stevenson: https://youtu.be/7IkUgCLr5oA
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