Is it possible to search for all keys that start with a certain prefix in Aerospike? - database

So I am looking to model our existing redis data into aerospike. One requirement that we have is to be able to get all the keys for a given user. For eg., say we have keys such as <id>:<timestamp>. Now, at some point in time, I need to get all keys for the given id, where I would require something like a prefix search across all keys in the aerospike namespace (which are indexed) to get the values for all <id>:<timestamp> keys. Would like to know if this is possible, and if yes, how.

You cannot do a query on key prefix directly. The server only stores the key digest, so the key value (<id>:<timestamp> in your case) doesn't get indexed.
The way to model this would be to add the <id> part of your key as a separate record bin. Then you can index that bin and run a query on it.
Here is a simple example - it's using the Aerospike Node.js client but the concept is the same no matter what client you prefer:
const Aerospike = require('aerospike')
const ns = 'test'
const set = 'demo'
// connect to cluster and create index on 'id' bin
var client = Aerospike.client()
client.connect((err) => {
assertOk(err, 'connecting to cluster')
createIndex('id', 'id_idx', Aerospike.indexDataType.STRING, () => {
// create a new sample record
var userId = 'user1'
var ts = new Date().getTime()
var key = new Aerospike.Key(ns, set, `${userId}:${ts}`)
var record = { id: userId, value: Math.random() }
client.put(key, record, (err) => {
assertOk(err, 'write record')
// query for records with matching 'id'
var query = client.query(ns, set)
query.where(Aerospike.filter.equal('id', userId))
var stream = query.foreach()
stream.on('error', (error) => assertOk(error, 'executing query'))
stream.on('end', () => client.close())
stream.on('data', (record, meta, key) => {
console.log(record)
})
})
})
})
function assertOk (err, message) {
if (err) {
console.error('ERROR: %s - %s', message, err)
process.quit()
}
}
function createIndex (bin, name, datatype, callback) {
var index = {
ns: ns,
set: set,
bin: bin,
index: name,
datatype: datatype
}
client.createIndex(index, (err, job) => {
assertOk(err, 'creating index')
job.waitUntilDone(100, (err) => {
assertOk(err, 'creating index')
callback()
})
})
}
Hope this helps!

Related

How to get all subcollection documents with subcollection name as a date? [duplicate]

Say I have this minimal database stored in Cloud Firestore. How could I retrieve the names of subCollection1 and subCollection2?
rootCollection {
aDocument: {
someField: { value: 1 },
anotherField: { value: 2 }
subCollection1: ...,
subCollection2: ...,
}
}
I would expect to be able to just read the ids off of aDocument, but only the fields show up when I get() the document.
rootRef.doc('aDocument').get()
.then(doc =>
// only logs [ "someField", "anotherField" ], no collections
console.log( Object.keys(doc.data()) )
)
It is not currently supported to get a list of (sub)collections from Firestore in the client SDKs (Web, iOS, Android).
In server-side SDKs this functionality does exist. For example, in Node.js you'll be after the ListCollectionIds method:
var firestore = require('firestore.v1beta1');
var client = firestore.v1beta1({
// optional auth parameters.
});
// Iterate over all elements.
var formattedParent = client.anyPathPath("[PROJECT]", "[DATABASE]", "[DOCUMENT]", "[ANY_PATH]");
client.listCollectionIds({parent: formattedParent}).then(function(responses) {
var resources = responses[0];
for (var i = 0; i < resources.length; ++i) {
// doThingsWith(resources[i])
}
})
.catch(function(err) {
console.error(err);
});
It seems like they have added a method called getCollections() to Node.js:
firestore.doc(`/myCollection/myDocument`).getCollections().then(collections => {
for (let collection of collections) {
console.log(`Found collection with id: ${collection.id}`);
}
});
This example prints out all subcollections of the document at /myCollection/myDocument
Isn't this detailed in the documentation?
/**
* Delete a collection, in batches of batchSize. Note that this does
* not recursively delete subcollections of documents in the collection
*/
function deleteCollection(db, collectionRef, batchSize) {
var query = collectionRef.orderBy('__name__').limit(batchSize);
return new Promise(function(resolve, reject) {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
}
function deleteQueryBatch(db, query, batchSize, resolve, reject) {
query.get()
.then((snapshot) => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = db.batch();
snapshot.docs.forEach(function(doc) {
batch.delete(doc.ref);
});
return batch.commit().then(function() {
return snapshot.size;
});
}).then(function(numDeleted) {
if (numDeleted <= batchSize) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(function() {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
This answer is in the docs
Sadly the docs aren't clear what you import.
Based on the docs, my code ended up looking like this:
import admin, { firestore } from 'firebase-admin'
let collections: string[] = null
const adminRef: firestore.DocumentReference<any> = admin.firestore().doc(path)
const collectionRefs: firestore.CollectionReference[] = await adminRef.listCollections()
collections = collectionRefs.map((collectionRef: firestore.CollectionReference) => collectionRef.id)
This is of course Node.js server side code. As per the docs, this cannot be done on the client.

AngularJS file upload, Express and node-postgres (pg)

Got a component in AngularJS (porting soon to Angular 7) for updating a user profile that invokes an AngularJS service method to perform a PUT to /api/user/:id.
Want to add a small photo (<100K) to send in the same PUT with the other fields then handle the request like this in the controller...
// route: PUT /api/user/:id
import db from '../../utils/db';
export async function upsert(req, res) {
const { id, name, phone, email, photo } = req.body;
// users.photo in PostgreSQL has datatype of bytea
const sql = `UPDATE users SET name = $2, phone = $3, email = $4, photo = $5) WHERE id = $1 RETURNING id, name, phone, email, photo;`;
const { rows } = db.query(sql, [id, name, phone, email, photo];
return res.status(200).send(rows);
}
Is there a clean way to encode the image client-side so it can be included in the JSON the AngularJS service PUTs? Other solutions I found seem like overkill for this use-case - requiring the image upload to be handled very differently from the other fields.
Ended up biting the bullet - creating a separate table for files along with its own API using formidable and node-postgres. In case this helps anyone else, here's how it turned out.
PostgreSQL data definition...
-- DROP SEQUENCE public.files_seq;
CREATE SEQUENCE IF NOT EXISTS public.files_seq;
-- DROP TABLE public.files;
CREATE TABLE IF NOT EXISTS public.files (
_id integer PRIMARY KEY DEFAULT nextval('files_seq'::regclass),
name character varying(512) NOT NULL,
type character varying(20) NOT NULL,
data bytea
);
-- DROP INDEX public.users_first_name;
CREATE INDEX files_name ON public.files USING btree (name);
Controller for Express...
import stream from 'stream';
import fs from 'fs';
import { IncomingForm } from 'formidable';
import db from '../../utils/db';
// Returns list of images
export async function index(req, res) {
const { rows } = await db.query('SELECT _id, name, type FROM files ORDER BY name;', []);
return res.send(rows);
}
// Uploads a single file
export async function upload(req, res) {
let _id;
new IncomingForm().parse(req, (err, fields, files) => {
if(err) throw err;
if(Array.isArray(files)) throw new Error('Only one file can be uploaded at a time');
const { name, type, path } = files.file;
fs.readFile(path, 'hex', async(err, fileData) => {
if(err) throw err;
fileData = `\\x${fileData}`;
const sql = 'INSERT INTO files (name, type, data) VALUES($1, $2, $3) RETURNING _id;';
const { rows } = await db.query(sql, [name, type, fileData]);
_id = rows[0]._id;
res.send({ id: _id });
// console.log(`Uploaded ${name} to ${path} and inserted into database (ID = ${_id})`);
// No need to delete the file uploaded as Heroku has an ephemeral file system
});
});
}
// Downloads a file by its _id
export async function download(req, res) {
const _id = req.params.id;
const sql = 'SELECT _id, name, type, data FROM files WHERE _id = $1;';
const { rows } = await db.query(sql, [_id]);
const file = rows[0];
const fileContents = Buffer.from(file.data, 'base64');
const readStream = new stream.PassThrough();
readStream.end(fileContents);
res.set('Content-disposition', `attachment; filename=${file.name}`);
res.set('Content-Type', file.type);
readStream.pipe(res);
return rows[0];
}
// Deletes a file from the database (admin-only)
export async function destroy(req, res) {
const _id = req.params.id;
const sql = 'DELETE FROM files WHERE _id = $1;';
await db.query(sql, [_id]);
res.status(204).send({ message: `File ${_id} deleted.`});
}
On the client side, I'm using ng-file-upload with AngularJS.
Here's the relevant part of the view (in pug for brevity)...
.form-group.col-md-6
label(for='photo') Teacher photo
input.form-control(ngf-select='$ctrl.uploadPhoto($file)', type='file', id='photo', name='photo', ng-model='$ctrl.user.photo', ngf-pattern="'image/*'", ngf-accept="'image/*'", ngf-max-size='100KB', ngf-min-height='276', ngf-max-height='276', ngf-min-width='236', ngf-max-width='236', ngf-resize='{width: 236, height: 276}', ngf-model-invalid='errorFile')
ng-messages.help-block.has-error(for='form.photo.$error', ng-show='form.photo.$dirty || form.$submitted', role='alert')
ng-message(when='maxSize') Please select a photo that is less than 100K.
ng-message(when='minHeight,maxHeight,minWidth,maxWidth') The image must be 236 x 276 pixels.
span(ng-if='$ctrl.user.imageId')
img(ng-src='/api/file/{{ $ctrl.user.imageId }}' alt="Photo of Teacher")
and method in its controller...
uploadPhoto(file) {
if(file) {
this.uploadService.upload({
url: '/api/file/upload',
data: { file }
})
.then(response => {
this.user.imageId = response.data.id;
}, response => {
if(response.status > 0) console.log(`${response.status}: ${response.data}`);
}, evt => {
// Math.min is to fix IE which reports 200% sometimes
this.uploadProgress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total, 10));
});
}
}

How to for loop all documents in a collection - Azure CosmosDB - Nodejs

I have looked around at a few answers/questions regarding this issue but yet to find a solution.
I have a collection with documents (simplified) as such:
{
"id": 123
"stuff": "abc"
"array":[
{
"id2":456
"properties": [
{
"id3": 789
"important": true
}
]
}
]
}
I want to check for each document in my collection, for each array object within array, for each properties, if it has important: true for example. Then return:
"id": 123
"id2": 456
"id3": 789
I have tried using:
client.queryDocuments(self.collection._self, querySpec).toArray(function(err, results) {
if (err) {
callback(err);
} else {
callback(null, results[0]);
}
});
But the issue is an array has a maximum character limit. If my collection has millions of documents, this would presumably be exceeded. (Javascript Increase max array size)
Or, am I misunderstanding the above question? Is it talking about the number of objects in an array (of which, each can have unlimited object character length?)
Thus I am looking a for loop-esque solution, where each document is returned, I do my analysis, then move to then next/do them in parallel.
Any insight would be greatly appreciated.
But the issue is an array has a maximum character limit. If my
collection has millions of documents, this would presumably be
exceeded. (Javascript Increase max array size)
Based on my research,the longest possible array in js could have 232-1 = 4,294,967,295 = 4.29 billion elements. However, it is perfectly enough to meet your millions data volume requirements. In addition,you can't query such huge volume data directly surely,that's impossible you do that.
Whether about throughput constraints(RUs settings) or query efficiency factors, you should consider batching large volumes of data anyway.
Thus I am looking a for loop-esque solution, where each document is
returned, I do my analysis, then move to then next/do them in
parallel.
Maybe you could use v2 js sdk for cosmos db sql api.Please refer to the sample code:
const cosmos = require('#azure/cosmos');
const CosmosClient = cosmos.CosmosClient;
const endpoint = "https://***.documents.azure.com:443/"; // Add your endpoint
const masterKey = "***"; // Add the masterkey of the endpoint
const client = new CosmosClient({ endpoint, auth: { masterKey } });
const databaseId = "db";
const containerId = "coll";
async function run() {
const { container, database } = await init();
const querySpec = {
query: "SELECT r.id,r._ts FROM root r"
};
const queryOptions = {
maxItemCount : -1
}
const queryIterator = await container.items.query(querySpec,queryOptions);
while (queryIterator.hasMoreResults()) {
const { result: results, headers } = await queryIterator.executeNext();
console.log(results)
console.log(headers)
//do what you want to do
if (results === undefined) {
// no more results
break;
}
}
}
async function init() {
const { database } = await client.databases.createIfNotExists({ id: databaseId });
const { container } = await database.containers.createIfNotExists({ id: containerId });
return { database, container };
}
run().catch(err => {
console.error(err);
});
More details about continuation token ,please refer to my previous case.Any concern,please let me know.
I am using Cosmos DB SQL API Node.js library. I am unable to find the Continuation Token from this library so that I can return it to client. The idea is to get it back from the client for the next pagination request.
I have a working code which iterates multiple times to get all the documents. What changes will be required here to get the continuation token?
function queryCollectionPaging() {
return new Promise((resolve, reject) => {
function executeNextWithRetry(iterator, callback) {
iterator.executeNext(function (err, results, responseHeaders) {
if (err) {
return callback(err, null);
}
else {
documents = documents.concat(results);
if (iterator.hasMoreResults()) {
executeNextWithRetry(iterator, callback);
}
else {
callback();
}
}
});
}
let options = {
maxItemCount: 1,
enableCrossPartitionQuery: true
};
let documents = []
let iterator = client.queryDocuments( collectionUrl, 'SELECT r.partitionkey, r.documentid, r._ts FROM root r WHERE r.partitionkey in ("user1", "user2") ORDER BY r._ts', options);
executeNextWithRetry(iterator, function (err, result) {
if (err) {
reject(err)
}
else {
console.log(documents);
resolve(documents)
}
});
});
};

Output all documents in mongoose

I am using mongoose ODM and have a schema which looks like this:
var banSchema = new Schema({
userid: { type: String, required: true, unique: true },
name: String,
groupid: String,
reason: String,
timestamp: Date
});
I want to output every single user id from all documents in the collection. I am using this query to obtain the userid objects. However I cannot seem to get the full list automatically. I have to manually enter the object number as seeen below:
bot.onText(/\/sync/i, function (msg) {
var fromId = msg.from.id;
var chatId = msg.chat.id;
if (fromId == config.sudo) {
console.log('Sudo Confirmed And Authorized!');
Ban.find({}, function (err, obj) {
console.log(obj[0].userid); // Returns A Single ID
console.log(obj[1].toObject().userid); // Returns a different ID
bot.sendMessage(chatId, obj[1].toObject().useridid);
});
} else {
console.log('Someone Is Trying To Act Like Sudo! *sigh*');
bot.sendMessage(chatId, 'You Are Not A Mod!');
}
});
This however does not return a full list of id's as I want. How could I solve this issue?
The code above is for a telegram bot which on a /sync command it should return a message with all ids from the collection.
Telegram bot API Limits
Due to the API limits, the entire output should be in a single message.
var query = Ban.find({}).select({
"userid": 1,
//Add more column fields here
"_id": 0 //Ensures _id is not displayed
});
var arr = [];
query.exec(function (err, results) {
if (err) throw err;
results.forEach(function (result) {
arr.push(result.userid);
// Add more column fields here;
});
var fixedJoin =arr.join("\n");
console.log(fixed);
bot.sendMessage(chatId, 'List\n\n' + fixedJoin);
});
The easiest way to get all values of a particular field across all docs in the collection is to use distinct:
Ban.distinct('userid', function (err, userids) {
// userids is an array containing all userid values in the collection.
// string.join into a single string for the message.
bot.sendMessage(chatId, 'USER IDs\n\n' + userids.join('\n'));
});
Use this syntax
Ban.find({}).
select('userid').
exec(function(err, result) {
//result is array of userid of all document
});
You can use this syntax:
Ban.find({}, 'userid', function(err, users) {
users.forEach(function(user) {
console.log(user);
bot.sendMessage(chatId, 'users \n' + user);
});
})

How to update mongoDB from post()

I am creating a MEAN Stack application. My post function is as follows:
app.post('/updateGroup/:id', function(req, res) {
var id = req.params.id; // = mongoDB ObjectID ie: "55616e2a37e8728266ceac6"
var vals = {};
vals['hostName'] = req.body.hostName // = a String ie, "Steve"
// this is a different name value than the
// current hostName key that is in
// the groupList db
db.groupList.update(
{"_id": id},
{$set : vals},
function(err, result) {
if (err) {
console.log(err);
}
else {
console.log(result);
}
}
);
});
When I access this function in my front-end Angular code my
console.log(result);
Comes out as:
{ ok: true, n: 0, updatedExisting: true }
But I should see n: 1 to indicate there was an update? Why is my Node application not updating my mongoDB key:value pair?
Is there something about db.collection.update() that I'm missing?
I was able to figure it out:
When assigning the _id query I needed to do it like this:
id['_id'] = mongojs.ObjectId(req.params.id);

Resources