How to execute transaction with massive.js - node.js

I'm trying to achieve a very simple thing:
START TRANSACTION;
DELETE FROM table WHERE id = 1;
ROLLBACK;
Running this on the postgres database works perfectly. With massive.js it doesn't:
this.db.run(
"START TRANSACTION",
[]
);
setTimeout(() => {
this.db.run(
"DELETE FROM table WHERE id = $1"
[1]
);
}, 2000);
setTimeout(() => {
this.db.run(
"ROLLBACK;"
[]
);
}, 4000);
It doesn't rollback the changes, just deletes from the database. COMMIT doesn't work as well. What's wrong?
Is there some way to dump queries order?

Massive uses pg-promise underneath, which in turn supports transactions:
db.instance.tx(t => {
// BEGIN has been executed
return t.none('DELETE FROM table WHERE id = $1', [123])
.then(() => {
// Records have been deleted within the transaction
throw new Error('Random error to fail the transaction');
// = the same as returning Promise.reject(new Error('Random...'))
});
})
.catch(error => {
// ROLLBACK has been executed, no records end up deleted.
console.log(error);
});
See also:
Accessing the Driver
Monitoring Queries
Is there some way to dump queries order?
Monitoring Queries shows you how to do it, or you can add event query handler within db.driverConfig object when initializing massive.

Related

Multiple queries on Firestore CollectionReference and QuerySnapshot: cloud functions in node.js

Using Cloud Functions and node.js, I have a Firestore collection that I am querying and then selecting one random document from the returned documents. The problem is that I can't seem to query the QuerySnaphot that is returned from the initial query and I can't think of another way of going about it. For context, what I want to do is get the server time and minus 30 days from it, I then want to query the collection available-players for all player documents that have a timestamp of more than 30 days ago in the last_used field (which contains a timestamp) and then run some logic (that I know works independently so no need to show it all here).
const availablePlayers = db.collection("available-players");
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);
const key = availablePlayers.doc().id;
// query the last_used field
const returnedPlayers = availablePlayers.where(last_used, "<=", cutoffTime);
// this now doesn't work
// it does work if I don't run the above query and just query availablePlayers
returnedPlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
.then(snapshot => {
if(snapshot.size > 0) {
// do some stuff
})
});
}
else {
const player = returnedPlayers.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
// do some stuff
})
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
})
.catch(err => {
console.log('Error getting documents', err);
});
The idea is that I want to run the query, get the returned documents meeting the time criteria, generate a key from these, then use the key to select a random document. Is this because I am trying to query a QuerySnapshot, whereas just querying availablePlayers works because I'm querying a CollectionReference? How do I get around this? Any help would be greatly appreciated!

Create nested Transaction in mssql using NodeJS

I am trying to create nested transactions in NodeJs using MSSQL NPM. I am using following code block
var sqlModule = require("mssql");
var sqlManagerClass = require("./sqlManager.js");
var sql = new sqlManagerClass(sqlModule);
sql.setConfig({
user: "dbo",
password: "***********",
server: "127.0.0.1",
database: "dbname",
requestTimeout: 120000,
});
sql.beginTransaction(function (transaction) {
if (transaction == null) {
callback("Unable to Start Transaction", null);
return;
}
sql.beginTransaction(function (newTransaction) {
if (newTransaction == null) {
callback("Unable to Start newTransaction", null);
return;
}
sql.execute(
"dbname.dbo.insert_dest",
[],
[],
function (err, results) {
console.log(results);
newTransaction.commit(function (err) {
if (err) {
console.log(err);
}
sql.execute("dbname.dbo.select_dest", [], [], function (
err,
results2
) {
if (err) {
console.log(err);
return;
}
console.log(results2);
transaction.rollback(function (rollBackErr) {
if (rollBackErr) {
console.log(rollBackErr);
}
sql.execute("dbname.dbo.select_dest", [], [], function (
err,
results2
) {
if (err) {
console.log(err);
return;
}
console.log(results2);
console.log('end')
});
});
});
});
},
newTransaction
);
});
});
creating transaction
this.beginTransaction = function(callback, transaction) {
// if the optional transaction argument is passed in, we are already working with a transation, just return it
if (typeof transaction !== "undefined") {
callback(transaction);
return;
}
self.connectSql(function(err) {
var transaction = new self.sql.Transaction(self.connection);
transaction.begin(function(err) {
// ... error checks
if (err) {
self.log("SQLManager - Error Beginning Transaction " + err);
callback(null);
}
// callback with the transaction handler
callback(transaction);
});
});
}
I have to create 2 transactions so that I can start a transaction and perform a set of operations on different stored procedures id any thing goes wrong I can revert back to my original state using the first transaction.
In short I am trying to achieve something like following SQL code
BEGIN TRAN T1;
BEGIN TRAN M2
INSERT INTO dbo.dest
(
Code,
Text,
Type
)
VALUES
( 'l', -- Code - varchar(50)
'Love', -- Text - varchar(50)
2 -- Type - int
)
COMMIT TRAN M2
// I am calling another Sp which is similar to this
SELECT * FROM dbo.dest
//validation the code
//if error
ROLLBACK TRAN T1
My question is how can I create nested transaction so that I can revert back entire transaction if any error occur after the first transaction complete. Thank you.
First read this
A SQL Server DBA myth a day: (26/30) nested transactions are real
To see if SQL Server "nested transactions" or savepoints are really going to help you.
Then you'll probably have to handle this all yourself by issuing the transaction control statements using sql.execute() eg:
sql.execute("begin transaction");
//something that works
sql.execute("save transaction MyTran");
//something that fails
sql.execute("rollback transaction MyTran");
sql.execute("commit transaction");
how can I create nested transaction so that I can revert back entire transaction if any error occur after the first transaction complete
What you have will do that. Nested transactions are not real, so calling
begin tran
...
begin tran --<-- ##trancount += 1, but nothing else really happens
...
commit tran --<--nothing is really commited yet. ##trancount -= 1
...
rollback tran --<-- all work is rolled back

Is it possible to run sqlite queries conditionally in node.js?

I'm using node v14.2 and sqlite3: https://github.com/mapbox/node-sqlite3
I'm trying to determine if a table exists, and if it does, make a query against it. I've tried:
data = []
db.run("SELECT name FROM sqlite_master WHERE type='table' AND name='mytable';", result => {
console.log(result)
if (result) {
db.each("SELECT * FROM mytable;", (err, row) => {
data.push(row)
})
}
})
console.log(data)
However, my array data never gets pushed to, even when the table mytable exists. I've also tried testing for table existence with PRAGMA table_info('mytable');. How could I add to data once I confirm that mytable exists?
The issue I see in your code is, the console.log is outside of the callback function.
data = []
db.all("SELECT name FROM sqlite_master WHERE type='table' AND name='mytable';", function(err, rows) {
rows.forEach(function (row) {
data.push(row)
});
console.log(data)
});
What you need to understand in JS is, the callbacks are async and might get called after the next line gets executed. In your case, most likely the console.log got executed before the call back finished.
Update: Just realised the run function does not return anything.
I figured out the problem. db.run()'s second argument, result, is null if the query was successful and error if it was not. Thus, I should error if result, rather than the opposite. Here's the working code:
data = []
db.run("SELECT name FROM sqlite_master WHERE type='table' AND name='mytable';", result => {
if (result) {
console.error(result)
} else {
db.each("SELECT * FROM mytable;", (err, row) => {
data.push(row)
})
}
console.log(data)
})

inserting and indexing in Couchdb at the same time or consequently

I am new to couchdb and trying to save some docs at the same time indexing them.
Currently what I do is the following:
app.post("/api/result-store/v1/results/:searchID", (req, res) => {
const dbName = "test_" + req.params.searchID;
database.checkAndCreateDatabase(dbName).then(
db => {
const docs = req.body.objects;
db.bulk({ docs }).then(
body => {
res.send({
writes: body.reduce((total, currentValue) => {
return total + (currentValue.ok ? 1 : 0);
}, 0)
});
},
err => {
res.send(err);
}
);
},
err => {
console.log(err);
}
);
});
So all I do above is saving but no indexing. Now if I query the database like:
{{url}}/api/result-store/v1/results/jmeter_test_db_size_90_k?q=*:*&limit=200&counts=["qid_name", "datasource"]
Then the indexing will start. But this is too late since indexing takes time and the customer needs to wait and wait until the indexing is done to get the result.
I am thinking to start indexing the data as soon as I insert the docs so saving and indexing at the same time or consequently. Is it possible at all? Any insight is appreciated
Since you're using nano, you can create the index by invoking db.createIndex just after creating the database (creates an index on database fields, as specified in CouchDB doc).

"before all" hook randomly showing up in my tests

I'm currently running a stack that consists of Express and MongoClient with Mocha and Chai for testing. I'm working on writing test cases for my endpoint and am getting a random error that pops up from time to time. Below is a snippet of one of the suits I'm writing:
describe('Recipes with populated database', () => {
before((done) => {
var recipe1 = {"search_name": "mikes_mac_and_cheese", "text_friendly_name": "Mikes Mac and Cheese","ingredients": [{"name": "elbow_noodles","text_friendly_name": "elbow noodles","quantity": 12,"measurement": "oz"},{"name": "cheddar_cheese","text_friendly_name": "cheddar cheese","quantity": 6,"measurement": "oz"},{"name": "gouda_cheese","text_friendly_name": "gouda cheese","quantity": 6,"measurement": "oz"},{"name": "milk","text_friendly_name": "milk","quantity": 2,"measurement": "oz"}],"steps": ["Bring water to a boil","Cook noodels until al dente.","Add the milk and cheeses and melt down.","Stir constantly to ensure even coating and serve."],"course": ["dinner","lunch","side"],"prep_time": {"minutes": 15,"hours": 0},"cook_time":{"minutes": 25,"hours": 1},"cuisine": "italian","submitted_by": "User1","searchable": true};
db.collectionExists('recipes').then((exists) => {
if (exists) {
db.getDb().dropCollection('recipes', (err, results) => {
if (err)
{
throw err;
}
});
}
db.getDb().createCollection('recipes', (err, results) => {
if (err)
{
throw err;
}
});
db.getDb().collection('recipes').insertOne(recipe1, (err, result) => {
done();
});
});
});
The collectionExists() method simply takes in a name and returns a promise that is resolved to a true/false value. I've already done some debugging and it is working just fine. Where I am getting a problem is when I hit the section of the code where I call createCollection. I get an error about how the collection already exists thus leading to my tests failing. This appears to be happening on every third time I'm running my tests as well.
The purpose of all this is to ensure that my database collection called recipes is completely empty before I start testing so I'm not stuck with old data or in an uncontrolled environment.
You have a race condition between .createCollection and .insertOne. In other words, they start at the same time and go in parallel. There is no way to tell which will be done first.
The way .insert works in MongoDB is that if the collection is missing and you try inserting - it's going to create a collection. So if .insertOne is executed first - the collection is created and that is why you're getting the already exists error in an attempt to createCollection.
Due to the async nature of DB calls you'd have to place the subsequent calls inside the callback of a prev. one. This way there will be no parallel execution:
before((done) => {
var recipe1 = {/**/};
db.collectionExists('recipes')
.then((exists) => {
if (exists) {
// Drop the collection if it exists.
db.getDb().dropCollection('recipes', (err) => {
if (err) {
// If there's an error we want to pass it up to before.
done(err);
return;
}
// Just insert a first document into a non-existent collection.
// It's going to be created.
// Notice the done callback.
db.getDb().collection('recipes').insertOne(recipe1, done);
});
}
// If there were no such collection - simply insert the first doc to create it.
// Note that I'm passing before's done callback inside.
db.getDb().collection('recipes').insertOne(recipe1, done);
})
// We don't want to lose the error from this promise always.
.catch(err => done(err));
});
But. Actually, there is no need to drop and re-create a collection each time you run the tests. You can simply .remove all the objects in the before block. So probably the right solution would be:
before((done) => {
var recipe1 = {/**/};
const recipes = db.getDb().collection('recipes');
// Simply wipe out the data
recipes.remove({}, err => {
if (err) {
done(err);
return;
}
recipes.insertOne(recipe1, done);
});
});

Resources