How do i get table data from SQLite Database in Javascript? - javascript

I am working on a money system in a discord bot, i need to store data in a sqlite database, but i can't seem to read the data, only write.
Code:
const sqlite3 = require("sqlite3");
const moneyDB = new sqlite3.Database("./databases/money.db");
...
if(cmd == "money"){
const person = msg.mentions.users.first()
if(!person) return;
var info = moneyDB.exec(`SELECT * FROM money WHERE userID = "${person.id}"`);
console.log(info);
};
I also tried using these: moneyDB.run moneyDB.get moneyDB.all moneyDB.each
They all output:
Database { open: true, filename: './databases/money.db', mode: 65542 }
Why is this the output and not the actual database data?
Is there a more efficient way to do this?
I've already made a different command for adding and updating data on the database, but can't seem to read anything.

these functions of the sqlite3 module return the database for chaining.
you need to pass a function as second argument into db.all
const sqlite3 = require("sqlite3");
const moneyDB = new sqlite3.Database("./databases/money.db");
...
if (cmd == "money") {
const person = msg.mentions.users.first()
if(!person) return;
moneyDB.all(`SELECT * FROM money WHERE userID = "${person.id}"`, (err, info) =>{
console.log(err, info)
});
};

Related

Override Mongoose save method to retry on `duplicate key error`

My Mongoose schema uses a custom _id value and the code I inherited does something like this
const sampleSchema = new mongoose.Schema({
_id: String,
key: String,
});
sampleSchema.statics.generateId = async function() {
let id;
do {
id = randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
} while (await this.exists({_id: id}));
return id;
};
let SampleModel = mongoose.model('Sample', sampleSchema);
A simple usage looks like this:
let mySample = new SampleModel({_id: await SampleModel.generateId(), key: 'a' });
await mySample.save();
There are at least three problems with this:
Every save will require at least two trips to the database, one to test for a unique id and one to save the document.
For this to work, it is necessary to manually call generateId() before each save. An ideal solution would handle that for me, like Mongoose does with ids of type ObjectId.
Most significantly, there is a potential race condition that will result in duplicate key error. Consider two clients running this code. Both coincidentally generate the same id at the same time, both look in the database and find the id absent, both try to write the record to the database. The second will fail.
An ideal solution would, on save, generate an id, save it to the database and on duplicate key error, generate a new id and retry. Do this in a loop until the document is stored successfully.
The trouble is, I don't know how to get Mongoose to let me do this.
Here's what I tried: Based on this SO Question, I found a rather old sample (using a very old mongoose version) of overriding the save function to accomplish something similar and based this attempt off it.
// First, change generateId() to force a collision
let ids = ['a', 'a', 'a', 'b'];
let index = 0;
let generateId = function() {
return ids[index++];
};
// Configure middleware to generate the id before a save
sampleSchema.pre('validate', function(next) {
if (this.isNew)
this._id = generateId();
next();
});
// Now override the save function
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let retryOnDuplicate = function(err, savedDoc) {
if (err) {
if (err.code === 11000 && err.name === 'MongoError') {
self.save(options, retryOnDuplicate);
return;
}
}
if (callback) {
callback(err, savedDoc);
}
};
return self.save_original(options, retryOnDuplicate);
}
This gets me close but I'm leaking a promise and I'm not sure where.
let sampleA = new SampleModel({key: 'a'});
let sampleADoc = await sampleA.save();
console.log('sampleADoc', sampleADoc); // prints undefined, but should print the document
let sampleB = new SampleModel({key: 'b'});
let sampleBDoc = await sampleB.save();
console.log('sampleBDoc', sampleBDoc); // prints undefined, but should print the document
let all = await SampleModel.find();
console.log('all', all); // prints `[]`, but should be an array of two documents
Output
sampleADoc undefined
sampleBDoc undefined
all []
The documents eventually get written to the database, but not before the console.log calls are made.
Where am I leaking a promise? Is there an easier way to do this that addresses the three problems I outlined?
Edit 1:
Mongoose version: 5.11.15
I fixed the problem by changing the save override. The full solution looks like this:
const sampleSchema = new mongoose.Schema({
_id: String,
color: String,
});
let generateId = function() {
return randomStringGenerator.generate({length: 8, charset: 'hex', capitalization: 'uppercase'});
};
sampleSchema.pre('validate', function() {
if (this.isNew)
this._id = generateId();
});
let SampleModel = mongoose.model('Sample', sampleSchema);
SampleModel.prototype.save_original = SampleModel.prototype.save;
SampleModel.prototype.save = function(options, callback) {
let self = this;
let isDupKeyError = (error, field) => {
// Determine whether the error is a duplicate key error on the given field
return error?.code === 11000 && error?.name === 'MongoError' && error?.keyValue[field];
}
let saveWithRetries = (options, callback) => {
// save() returns undefined if used with callback or a Promise otherwise.
// https://mongoosejs.com/docs/api/document.html#document_Document-save
let promise = self.save_original(options, callback);
if (promise) {
return promise.catch((error) => {
if (isDupKeyError(error, '_id')) {
return saveWithRetries(options, callback);
}
throw error;
});
}
};
let retryCallback;
if (callback) {
retryCallback = (error, saved, rows) => {
if (isDupKeyError(error, '_id')) {
saveWithRetries(options, retryCallback);
} else {
callback(error, saved, rows);
}
}
}
return saveWithRetries(options, retryCallback);
}
This will generate an _id repeatedly until a successful save is called and addresses the three problems outlined in the original question:
The minimum trips to the database has been reduced from two to one. Of course, if there are collisions, more trips will occur but that's the exceptional case.
This implementation takes care of generating the id itself with no manual step to take before saving. This reduces complexity and removes the required knowledge of prerequisites for saving that are present in the original method.
The race condition has been addressed. It won't matter if two clients attempt to use the same key. One will succeed and the other will generate a new key and save again.
To improve this:
There ought to be a maximum number of save attempts for a single document followed by failure. In this case, you've perhaps used up all the available keys in whatever domain you're using.
The unique field may not be named _id or you might have multiple fields that require a unique generated value. The embedded helper function isDupKeyError() could be updated to look for multiple keys. Then on error you could add logic to regenerate just the failed key.

How to create a module using mongoose for sharing a changing DB connection

i've been working on an app(Node.js with MongoDB using mongoose), and the server connects to 2 different databases, 1 generic containing username and password pairs for user authentication. Then, when the user signs in, I want to connect to a different database, named after the user's userId. I managed to create a module for sharing the generic UA database, but it's more difficult with the second one, since it doesn't open with the connection, but later on, when the user signs in. I guess i got inspired by the idea of react context kind of sharing.
So far i've got something like this
const mongoose = require("mongoose");
/*
UA = User Authentication
US = User Specific
DB = DataBase
*/
const UA_DB = mongoose.createConnection(/*...*/);
);
const User = UA_DB.model("User", require("../../data-schemas/user"));
let US_DB, Order, Item, Ingredient, Place;
console.log("opened UA database");
function sendUserId(newUserId) {
userId = newUserId;
US_DB = mongoose.createConnection(/*... ${newUserId} ...*/ );
Order = US_DB.model("Order", require("../../data-schemas/order"));
Item = US_DB.model("Item", require("../../data-schemas/item"));
Ingredient = US_DB.model(
"Ingredient",
require("../../data-schemas/ingredient")
);
Place = US_DB.model("Place", require("../../data-schemas/place"));
console.log("opened US database");
}
module.exports = {
UA_DB: {
User,
},
US_DB: {
Order,
Item,
Ingredient,
Place,
},
sendUserId,
};
Now, if I hadn't made it clear, the first, UA_DB works just fine, the user signs in just fine... When it comes to the US_DB i always get undefined as values(Cannot read property 'find' of undefined). I suspect the problem could be, that the exported value doesn't update with the value of the variables. Any ideas, how this could be solved?
Well, i figured it out. Instead of using precise values I use a function to return them, and to connect to the database.UserId is stored in a token, so after verification i check whether i am already connected to the right database (with the userId variable, which stores previous values) and then return curretn values of the models now my code looks something like this
const mongoose = require("mongoose");
/*
UA = User Authentication
US = User Specific
DB = DataBase
*/
const UA_DB = mongoose.createConnection(/* ... */
);
const User = UA_DB.model("User", require("../../data-schemas/user"));
let US_DB,
Order,
Item,
Ingredient,
Place = "some default value";
console.log("opened UA database");
let userId = "";
function getUS_DBModels(newUserId) {
if (newUserId !== userId) {
userId = newUserId;
US_DB = mongoose.createConnection(`...${userId}...`
);
Order = US_DB.model("Order", require("../../data-schemas/order"));
Item = US_DB.model("Item", require("../../data-schemas/item"));
console.log("opened a US_DB connection");
Ingredient = US_DB.model(
"Ingredient",
require("../../data-schemas/ingredient")
);
Place = US_DB.model("Place", require("../../data-schemas/place"));
}
return {
Order, Item, Ingredient, Place
}
}
module.exports = {
UA_DB: {
User,
},
getUS_DBModels,
};
For anyone wondering, in different modules you can access the values like this
const dbHandler = require("./path/to/the/module");
const { Item } = dbHandler.getUS_DBModels("UserId");

DiscordJS Database Not Storing Information Ideally

I'm trying to get the Discord bot to create a database that is basically the user map (a row for each user and columns for ID, nick name, avatar URL, etc) when it receives a !getdata command.
I've gotten to the point where the database successfully takes data, in this case the username and user ID, but it displays all the unique values in two columns as long comma separated values (i.e. the username column displays 'user1,user2,user3').
I'm sure this is by design, but I'm really struggling with restructuring. I'd like to either have it take all the data from an object map (client.users or message.guild.members) but I cannot figure it out.
The other option, which is what I'm trying now, is to create a row for each user and then fill in the values that I want to store, but I'm getting nowhere fast.
I'm very new with SQLite (and node/DiscordJS/JS for that matter), so any advice is greatly appreciated.
Index.js
const Discord = require('discord.js');
const client = new Discord.Client();
const sql = require('sqlite3');
let db = new sql.Database("users.sqlite", (err) => {
if (err) {
console.log('Error connecting to the database', err)
} else {
console.log('Database connected.')
}
})
let token = process.env.CLIENT_TOKEN;
let prefix = process.env.PREFIX ;
client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
db.run(`CREATE TABLE IF NOT EXISTS users(username TEXT, id TEXT)`);
})
client.on('message', function(message) {
if (!message.content.startsWith(prefix));
const args = message.content.slice(prefix.length).split(/ +/);
const command = args.shift().toLowerCase();
if (command === 'getdata') {
let username = message.guild.members.map(m=>m.user.username);
let userid = message.guild.members.map(m=>m.user.id);
db.run(`INSERT OR REPLACE INTO users(username, id) VALUES(?,?)`, [`${username}`,`${userid}`]);
return message.channel.send(`User database updated.`);
}
});
client.login(token);
If you're curious as to the formatting or way things are written, the answer is two fold:
I'm pretty new at this
This was the only way I could get the values in the database to return something other than null
Thanks in advance,
First off, welcome to the site.
I hope that I can shine some light here without diving into refactoring your code or making you change anything major.
One thing sticks out to me as to why you are storing an array instead of a single value.
let username = message.guild.members.map(m=>m.user.username);
let userid = message.guild.members.map(m=>m.user.id);
The .map call returns an array, not a single value.
Each user that issues a command is part of the message object. If I remember correctly, you would want this to be something like...
(simplified version)
const { username, id } = message.member.user;
db.run(`INSERT OR REPLACE INTO users(username, id) VALUES(?,?)`, [username, id]);
// ...
User documentation can be found here
Edit:
If you wanted to build the database for all users in that one command you could do something like the following with a bulk insert... (quick and dirty)
db.serialize(() => {
db.run('BEGIN TRANSACTION;');
// execute inserts in transaction
for (const m of message.guild.members) {
db.run('INSERT OR REPLACE INTO users(username, id) VALUES(?,?);', [m.user.username, m.user.id]);
}
// commit all inserts :)
db.run('COMMIT;')
});
message.channel.send('User database updated.');
Control flow documenation
Hopefully this points you in the right direction :)

Cloud functions - update data in firebase database

I'm using Cloud functions to count how many comments there are on a post.
When i add a comment it saves it in the firebase database and then on Cloud functions there is a functions that listen to "Comments" node and should "+1" back to firebase database.
For some reason it works only when i delete the comment from firebase database.
when i delete the comment its add "+1".
Thats my code
exports.commentsCount = functions.database.ref('/comments/{commentid}/{userUID}').onWrite(event =>{
const collectionRef = event.data.ref.parent;
const model = event.data.previous.val();
const commentid = event.params.commentid;
console.log("commentID:",commentid);
const countComments = collectionRef.child('countComments');
return countComments.transaction(current => {
console.log('Before the If');
if (!event.data.exists() && event.data.previous.exists()) {
console.log('Enter to the if.');
const commentsList = admin.database().ref(`comments/${commentid}/countComments`).transaction(current => {
return (current || 0) + 1;
});
}
}).then(() => {
console.log('Comments counter updated.');
});
});
Anyone can tell me where im doing wrong?
You're using this to determine when your function gets triggered:
exports.commentsCount = functions.database.ref('/comments/{commentid}/{userUID}').onWrite(event =>{
Key here is that you use onWrite, which means that this function gets triggered for any write operation under /comments/{commentid}/{userUID}.
Since you're only adding to the count, your function should only run when a new comment is added. For that you should use onCreate instead of onWrite:
exports.commentsCount = functions.database.ref('/comments/{commentid}/{userUID}').onCreate((snapshot, context) =>{
const collectionRef = snapshot.ref.parent;
const model = snapshot.val();
const commentid = context.params.commentid;
I also updated the parameters to match with the 1.0 Firebase Functions library. See the upgrade guide for more on that.

How can I check if same data exists before pushing new values in Firebase Database?

I am trying to develop a backend for my college dissertation project wherein I am developing an Attendance system for my college. My system will be working on the data generated by a time-table web-application already in use at my college, by which my system will identify the lectures in the time-table and take attendance for it.
The time-table application has a mysql backend but my application has a firebase backend.
So far, i am able to fetch data from mysql tables and push them into firebase database. But the problem is every-time I run the above code, it push the same already there data back again, which makes sense.
But what I want to achieve is to only add all the data once and then only add the new values or any updated values.
How should I tackle with this? I know, i have to add more checks before i push that data but I am really confused how should i tackle this hurdle.
// Connection to MySql sever.
mysql = require('mysql');
con = mysql.createConnection({
host: "localhost",
user: "root",
password: "root",
database: "mrbs"
});
// Connection to Firebase.
firebase = require('firebase');
firebase.initializeApp({
serviceAccount: "./SICSR-d924e501f52d.json",
databaseURL: "https://sicsr-d4771.firebaseio.com"
});
let fetchRecords = function(sql) {
return new Promise (function(resolve, reject) {
con.query(sql, function (err , result){
if(err) reject(err);
resolve(result);
});
});
};
fetchRecords("SELECT start_time, end_time, room_id, timestamp, Program, Batch, Course, Semester, Faculty, Division FROM mrbs_entry").then(function(fromResolve){
Resultset = fromResolve;
setRoomName(Resultset);
//console.log(global);
}).catch(function(fromReject){
console.log(fromReject);
});
//set room_id to the room_name retrive and send it forward to firebase.
function setRoomName(value){
let objValue = value;
for(let i = 0; i<objValue.length; i++) {
con.query("Select room_name , id from mrbs_room where id = '" + objValue[i].room_id + "'", function (err, result2){
if (err) throw err;
let j =0;
while(j<result2.length){
roomName = result2[j].room_name;
if(roomName == null && objValue[i] == null){
// TODO add some code here.
} else {
objValue[i].room_id = roomName;
saveRecord(objValue[i]);
}
j++;
}
});
}
}
function saveRecord(data){
let Resultset = [];
Resultset = data;
feedDatainFirebase(Resultset);
}
function feedDatainFirebase(value){
var Resultset = value;
let ref = firebase.database().ref("Lecture");
ref.push({
course_name: Resultset.Course,
program_name : Resultset.Program,
room_number : Resultset.room_id,
start_time : Resultset.start_time,
end_time : Resultset.end_time,
teacher_name : Resultset.Faculty,
timestamp : Resultset.timestamp
});
}
If you're wanting to sync from the mysql database to firebase, you'll need to have a key of some type that you can reference in each to tie the records together. I would start by querying only the records you want to sync in mysql. Then, loop through them and for each one, you'll want to update the reference in firebase based on the key you have defined.

Categories

Resources