QML Firebase startAt returns undefined - firebase

I am working on a 'typeahead’ type function which will check my Database with the current typed text to provide search suggestions of users using Felgo.
Here is the link for Felgos Firebase documentation
As to not search every entry I am looking to use the startAt and limitTo for a lower data use.
However when applying the startAt my searches only return undefined, I have tried testing this by changing my startAt from a variable to explicit data but this still only returns undefined.
My function is below:
function searchUsers(searchString) {
db.getValue("public/nameList/", {
orderByChild: true,
startAt: searchString, //searchString is a variable with my .currentText to search.
limitToFirst: 10,
}, function(success, key, value) {
if(success) {
searchArr = []
searchArr = value
console.debug("Read user value for key", key, "from DB:", value)
}
})
}
I have also tried by passing my var searchString through JSON.stringify(searchString) and also return undefined!
Removing the startAt: query entirely returns the entire result of nameList as expected, but no matter how I try to implement my startAt it always returns undefined.
A sample of my nameList JSON is:
nameList: {
"EddieLaw245" : 530343772383,
"EddieLawrence91" : 530343772385,
"EdwardL91" : 530343772386,
"EdwardLaw" : 530343772384,
"Edwardlawrence91" : 530343772380,
"JoBrownLondon" : 530343772381,
"KatiePrescottHair" : 543592635596,
"Tracey-Sweeting" : 530343772382
}
So with the above example, When I type E it should remove the last 3 entries, and so on.

The problem is that you're specifying orderByChild: true. If we look at the documentation of that:
orderByChild: If present, the queried object will have its properties ordered by values at sub-paths defined by the value of this property. Ordering by child properties makes the filter properties startAt, endAt and equalTo filter by the child property values
It may not be immediately clear from this, but orderByChild allows you to order the results on a property value under each of those nodes. So your code tries to order the child nodes on the value of a property true, which isn't possible (and should actually generate a compile-time error in the library) as the nodes under nameList don't have any child properties of their own. They merely have a key and a value.
What you're looking for is orderByKeys, which orders the child nodes on their keys. So:
db.getValue("public/nameList/", {
orderByKeys: true,
startAt: searchString,
limitToFirst: 10,
}
You'll typically also want to specify an endAt value, to ensure your type-ahead only shows values that start with the search string. If you only allow ASCII values in the keys, the simplest way to do this is:
startAt: searchString,
endAt: searchString + "~",
The ~ here is no magic operator, but merely the last ASCII characters. If you want to allow a broader character set, you'll need to use the last character in that character set - for example \uF7FF is the last code point for Unicode.
Update from OP
Though I'm certian Franks correct with typical Firebase usage; I suspect due to the Felgo plugin I am using the full solution has a slight adjustment;
db.getValue("public/nameList/", {
"orderByKey": true,
"startAt": searchString,
"endAt": searchString+"~",
"limitToFirst": 10,
}, function(success, key, value) {....}
})
Notes on the above - my filters/queries are surrounded by quotation marks "startAt", also instead of orderByKeys, I have used orderByKey

Related

findAll() returns empty with WHERE option

First question on StackOverflow, long time reader first time poster or whatever people say.
I'm developing a Discord bot in my free time using Discord.js, and I'm using Sequelize to interface with a local SQLite database. I can insert data into it just fine-- however, I can't seem to delete any of the records I add. Relevant piece of code is below, which I believe to be self-contradictory:
const query3 = await Towers.findAll({
attributes: ['channelID']
});
console.log(JSON.stringify(query3)); //returns the one Tower
console.log(query3[0].channelID === channel); //returns true(!)
const query2 = await Towers.findAll({
attributes: ['channelID'],
where: {channelID: channel}
});
console.log(JSON.stringify(query2)); //returns empty
//DELETE FROM Towers WHERE channelID = channel;
const query = await Towers.destroy({
where: {channelID: channel}
});
console.log(query); //returns 0, expected behavior given query2 returns empty
I'm attempting to delete a record from a table named Towers by passing a channel ID to it, which is expected to be unique. However, when I make any query on the database with a WHERE clause, the query returns an empty set-- even when, in this example, I sanity-checked and verified that the value I'm attempting to remove is present in the table. This occurs for both findAll() and findOne() as long as a WHERE clause is present.
(For posterity, I've double and triple checked that channelID was spelled correctly and with the correct capitalization in all instances.)
I'm happy to provide any more information if needed!
EDIT: As requested, the model definition...
const Towers = sequelize.define('Towers', {
serverID: {
type: Sequelize.INTEGER,
allowNull: false,
},
channelID: {
type: Sequelize.INTEGER,
unique: true,
allowNull: false,
},
pattern: Sequelize.STRING,
height: Sequelize.INTEGER,
delay: Sequelize.BOOLEAN,
});
channel in the snippet in the original post is defined as parseInt(interaction.options.getChannel('channel').id).
To anyone who happens to have the same issue I did, the answer is a doozy.
I wanted to store Discord server and channel ID's as integers, even though they're returned to you as strings when calling the API. As it turns out, Discord snowflakes are higher than float64 precision, which JS uses. When parsing the strings into integers to insert them into my table, the value changed from the intended number, and I was creating erroneous records.
In my case (with the actual numbers obfuscated) interaction.options.getChannel('channel').id returned "837512533934092340", while parseInt(interaction.options.getChannel('channel').id returned 837512533934092300. The number I was adding to the table was somehow 40 less!
I'm not sure if this could be fixed by using BigInt, but since it's going into a different structure anyway, I just shrugged and changed the serverId and channelId types to Sequelize.STRING in the model definition and removed the parseInt calls. Works like a charm now.
Good opportunity to shake my fist at JS though.

MikroORM Create filter query based on a value in the database

Simply put, is it possible to create a filter query where I reference a value stored in the row?
For example:
orm.em.findOne(Job, {
status: 'active',
startDate: {
$gt: '$anotherDateField'
}
}
My goal is to have a user-input defined filter (the status), but also only bring back certain rows where the start date is greater than another column's value.
You can use custom SQL fragment
orm.em.findOne(Job, {
status: 'active',
// expr helper allows to escape strict typing of the method, so we can use `em.raw()`
[expr('startDate')]: {
$gt: orm.em.raw('another_date_field') // this will have to be column name, not property name
}
}
Note that your em needs to be typed to the one exported from driver package to have access to the em.raw() method (if you work with orm instance, you need to type that to MikroORM<YourDriver> so orm.em can be properly typed).
https://mikro-orm.io/docs/entity-manager/#using-custom-sql-fragments

Firestore rule to only allow update on value change

I have a function that is evaluating if an update is allowed for a user's profile. As you can see, the validProfileUpdate function calls the validFieldUpdate function for each field I have listed (in this case name and age). When I execute an update command for just one of the fields it will work, but when I uncomment the second one (in this case, for age) it will always fail. I only want these fields to be allowed to update based on if there's a change in data between what's being sent in and what already exists.
function validProfileUpdate() {
let validKeys = ['name', 'age'];
return request.resource.data.diff(resource.data).changedKeys().hasOnly(validKeys) &&
validFieldUpdate('name', validName()) &&
validFieldUpdate('age', validAge());
}
function validFieldUpdate(field, condition) {
return !(field in request.resource.data.keys()) ||
(request.resource.data[field] != resource.data[field] && condition);
}
What I'm having difficulty with is that I figured the line !(field in request.resource.data.keys()) in validFieldUpdate would catch any fields not included in the update that's sent and it would return true but for some reason that's not happening when I add the second field age.
So in summary, this works and only allows updates after it's sent for names that aren't Joe:
const profilePayload = {
name: 'Joe'
}
await userProfileRef.update({ ...profilePayload })
But this is blocked by the rules 100% of the time:
const profilePayload = {
name: 'Joe'
age: 25
}
await userProfileRef.update({ ...profilePayload })
You can use affectedKeys to compare the items rather than changedKeys as changedKeys only accounts for key values that are different from the original, a problem if the value stays the same.
let validKeys = ['name', 'age'];
if request.resource.data.diff(resource.data).affectedKeys().hasOnly(validKeys);
Affected:
which lists any keys that have been added to, removed from or modified from the Map calling diff() compared to the Map passed to diff()
Changed:
which lists any keys that appear in both the Map calling diff() and the Map passed to diff(), but whose values are not equal.

DynamoDB update - "ValidationException: An operand in the update expression has an incorrect data type"

I am trying to append to a string set (array of strings) column, which may or may not already exist, in a DynamoDB table. I referred to SO questions like this and this when writing my UpdateExpression.
My code looks like this.
const AWS = require('aws-sdk')
const dynamo = new AWS.DynamoDB.DocumentClient()
const updateParams = {
// The table definitely exists.
TableName: process.env.DYNAMO_TABLE_NAME,
Key: {
email: user.email
},
// The column may or may not exist, which is why I am combining list_append with if_not_exists.
UpdateExpression: 'SET #column = list_append(if_not_exists(#column, :empty_list), :vals)',
ExpressionAttributeNames: {
'#column': 'items'
},
ExpressionAttributeValues: {
':vals': ['test', 'test2'],
':empty_list': []
},
ReturnValues: 'UPDATED_NEW'
}
dynamo.update(updateParams).promise().catch((error) => {
console.log(`Error: ${error}`)
})
However, I am getting this error: ValidationException: An operand in the update expression has an incorrect data type. What am I doing incorrectly here?
[Update]
Thanks to Nadav Har'El's answer, I was able to make it work by amending the params to use the ADD operation instead of SET.
const updateParams = {
TableName: process.env.DYNAMO_TABLE_NAME,
Key: {
email: user.email
},
UpdateExpression: 'ADD items :vals',
ExpressionAttributeValues: {
':vals': dynamo.createSet(['test', 'test2'])
}
}
A list and a string set are not the same type - a string set can only hold strings while a list may hold any types (including nested lists and objects), element types don't need to be the same, and a list can hold also duplicate items. So if your original item is indeed as you said a string set, not a list, this explains why this operation cannot work.
To add items to a string set, use the ADD operation, not the SET operation. The parameter you will give to add should be a set (not a list, I don't know the magic js syntax to specify this, check your docs) with a bunch of elements. If the attribute already exists these elements will be added to it (dropping duplicates), and if the attribute doesn't already exit, it will be set to the set of these elements. See the documentation here: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-UpdateExpression

Vuefire Firebase update issues

I'm having som issues with updating Firebase from VueFire. I m trying to use the following method, but it yells at me if I leave any field blank (which is supposed to happen often in setup) Any idea why this gets mad if .update with a blank field?
Error: Uncaught Error: Firebase.update failed: First argument contains undefined in property 'businesses.somebusiness.video'
updatePost(post) {
postsRef.child(post['.key']).update({
name: post.name,
video: post.video,
story: post.story,
cover: post.cover,
card: post.card
})
},
At one point I had the above re-written like so:
updatePost: function (post) {
const businesschildKey = post['.key'];
delete post['.key'];
/* Set the updated post value */
this.$firebaseRefs.posts.child(businesschildKey).set(post)
},
It worked amazingly but deleting the key seemed to cause weird ordering issues in Vue. I would prefer to stick with the top method if I can find a way to not have it trow an error if one is left blank.
According to this post,
When you pass an object to Firebase, the values of the properties can
be a value or null (in which case the property will be removed). They
can not be undefined, which is what you're passing in according to the
error.
Your error message suggests that post.video's value is undefined. You can use logical-or to provide a fallback value like so:
video: post.video || null,
That means whenever post.video has a false-y value, the expression will evaluate to null. That could catch empty string or numeric 0, though. To be more precisely correct, you should use
video: typeof post.video === 'undefined' ? null : post.video,
If you need to do this check for many values, you can write a function for it:
function nullIfUndefined(value) {
return typeof value === 'undefined' ? null : value;
}
then your expression would just be
video: nullIfUndefined(post.video),

Resources