flutter: Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object - flutter-web

import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import '../model/weather.dart';
class DatabaseHelper {
static final _databaseName = "Nithya.db";
static final _databaseVersion = 1;
static final favTable = 'favourites';
static final recentsTable = 'recents';
static final columnCityName = 'cityName';
static final columnCountry = 'country';
static final columnIcon = 'icon';
static final columnTemp = 'temp';
static final columnDescription = 'description';
// make this a singleton class
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
// only have a single app-wide reference to the database
static Database? _database;
Future<Database?> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed
_database = await _initDatabase();
return _database;
}
// this opens the database (and creates it if it doesn't exist)
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion, onCreate: _onCreate);
}
// SQL code to create the database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $favTable (
$columnCityName TEXT PRIMARY KEY NOT NULL,
$columnCountry TEXT NOT NULL,
$columnIcon TEXT NOT NULL,
$columnTemp TEXT NOT NULL,
$columnDescription TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE $recentsTable (
$columnCityName TEXT PRIMARY KEY NOT NULL,
$columnCountry TEXT NOT NULL,
$columnIcon TEXT NOT NULL,
$columnTemp TEXT NOT NULL,
$columnDescription TEXT NOT NULL
)
''');
}
// Helper methods
// Inserts a row in the database where each key in the Map is a column name
// and the value is the column value. The return value is the id of the
// inserted row.
Future<int> insert(Weather weath) async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.insert(favTable, {
'cityName': weath.cityName,
'country': weath.country,
'icon': weath.icon,
'temp': weath.temp,
'description': weath.description
});
});
return result;
}
Future<int> insertRecent(Weather weath) async {
Database db = await instance.database as Database;
var result;
await db.transaction((txn) async {
result = await db.insert(recentsTable, {
'cityName': weath.cityName,
'country': weath.country,
'icon': weath.icon,
'temp': weath.temp,
'description': weath.description
});
});
return result;
}
// All of the rows are returned as a list of maps, where each map is
// a key-value list of columns.
Future<List<Map<String, dynamic>>> queryAllRows() async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.query(favTable);
});
return result;
}
Future<List<Map<String, dynamic>>> queryAllRowsRecent() async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.query(recentsTable);
});
return result;
}
Future<int> delete(cityName) async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.delete(favTable,
where: '$columnCityName = ?', whereArgs: [cityName]);
});
return result;
}
Future<int> deleteRecent(cityName) async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.delete(recentsTable,
where: '$columnCityName = ?', whereArgs: [cityName]);
});
return result;
}
Future<int> deleteAll() async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.delete(favTable);
});
return result;
}
Future<int> deleteAllRecents() async {
var result;
Database db = await instance.database as Database;
await db.transaction((txn) async {
result = await db.delete(recentsTable);
});
return result;
}
}
This is my db helper class. It was working fine before but now it is causing this error
flutter: Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction.
How to solve this error. I'm new to flutter and sqflite please help.

What the error means is that in you code, instead of having:
await db.transaction((txn) async {
// DEADLOCK, don't use the db object below
result = await db.insert(favTable, {
you should do:
await db.transaction((txn) async {
// CORRECT, use the txn object instead
result = await txn.insert(favTable, {

Related

nodejs calling database calls parallel

The module calls three tables(students, subjects, grades)I call three sql queries and somehow managed to call one by one as explained below. the queries are independent of each other. However the select queries are executed one by one, first student, then subjects, then grades using await.
studentmodel.calls are only for executing select quesries from the database and is in one module. Other functions are defined in a separate module
The logic can execute the three selct queries(database calls) in parallel, then aggregate and process all the data together. Please let me know how to modify so that the database calls can execute independent, then move to process all data together
processing module -main start call
const mainstart = async () => {
let students = 0;
const getStudentData = await getAllStudents();
/** checking a condition if getEmployeeData responce is not empty */
if (getStudentData.length > 0) {
const studentData = await processData(getStudentData);
return 1;
} else {
return 0;
}
};
same file secondcall to the function getAllStudents
const getAllStudents = async () => {
try {
return await studentmodel.getAllStudents();//database call 1
} catch (err) {
// console.log("processing", err)
}
};
const processData = async (getStudentData) => {
try {
let studentData = [];
const subjectsData = await studentModel.getStudentSubjects();//database call 2
const gradesData = await studentModel.getStudentGrades();//database call 3
await Promise.all(getStudentData.map(async (singleObject) => {
let subjects = await processSubjects(subjectsData, singleObject.student_log);
let grades = await processGrades(gradesData, singleObject.student_log);
//Some processing on sigleobject, subjects and grades to populate studentData array
}));
return studentData;
} catch (err) {
console.log("processing", err)
}
};
const processSubjects = async (result, Name) => {
let subjectsArr = [];
const processArray = result.filter(ele => ele.student_log == Name)
processArray.map((singleObject) => {
subjectsArr.push({
name: singleObject.name,
startDate: singleObject.startDate,
});
})
return subjectsArr;
}
const processGrades = async (result, Name) => {
let gradesArr = [];
const processArray = result.filter(ele => ele.student_log == Name)
processArray.map((singleObject) => {
gradesArr.push({
id: singleObject.id,
name: singleObject.name,
});
})
return gradesArr;
database calls module/studentModel
const getAllStudents = async () => {
try {
/** Populating all students */
const sqlQuery = `SELECT * FROM STUDENTS`;
let [result] = await bigQuery.query({
query: sqlQuery,
location: 'US'
});
return result;
} catch (err) {
return false;
}
};
const getStudentSubjects = async () => {
try {
/** Populating all skills */
const sqlQuery = `SELECT * FROM Subjects`;
let [result] = await bigQuery.query({
query: sqlQuery,
location: 'US'
});
return result;
} catch (err) {
return false;
}
};
const getStudentGrades = async () => {
try {
/** Populating all students */
const sqlQuery = `SELECT * FROM GRADES`;
let [result] = await bigQuery.query({
query: sqlQuery,
location: 'US'
});
return result;
} catch (err) {
return false;
}
};
While I didn't probably fully understand what your question is, I had a go with your code.
I simulated your studentmodel functions with setTimeout and made the code like so that it first fetches all students. After fetching all students, it fetches the subjects and the grades "in parallel" by utilising Promise.all. After we have fetched our students, subjects and grades, we pass all of those to processData function where you can process all of the data however you want.
In case you would also like to fetch the students "in parallel" with the subjects and grades, just change the Promise.all part like so:
const [studentData, studentSubjects, studentGrades] = await Promise.all(
[
getAllStudents(),
getStudentSubjects(),
getStudentGrades()
]);
And remove the const studentData = await getAllStudents(); line and the if-clause. Because you had the if(studentData.length > 0) in your code, I assumed that we only want to fetch subjects and grades if there are students and therefore that needs to be done first, separately.
Note that if you want to do all three in parallel, you cannot use studentData when calling getStudentSubjects or getStudentGrades.
// STUDENTMODEL
const getAllStudents = async () => {
// Simulate SQL query
console.log("Fetching students");
return new Promise(resolve =>
setTimeout(() => {
resolve(["Student 1", "Student 2"])
}, 1000));
};
const getStudentSubjects = async () => {
// Simulate SQL query
console.log("Fetching subjects");
return new Promise(resolve =>
setTimeout(() => {
resolve(["Subject 1", "Subject 2"])
}, 1500));
};
const getStudentGrades = async () => {
// Simulate SQL query
console.log("Fetching grades");
return new Promise(resolve =>
setTimeout(() => {
resolve(["Grade 1", "Grade 2"])
}, 1500));
};
const mainstart = async () => {
// Fetch student data from database
const studentData = await getAllStudents();
if (studentData.length > 0) {
// Use Promise.all to wait until both student subjects and
// student grades have been fetched
// The results are destructured into
// studentSubjects and studentGrades variables
const [studentSubjects, studentGrades] = await Promise.all(
[
getStudentSubjects(studentData),
getStudentGrades(studentData)
]);
// Everything is fetched, process it all
processData([studentData, studentSubjects, studentGrades]);
return 1;
} else {
return 0;
}
};
const processData = (allData) => {
console.log("Processing all data");
console.log(allData);
// Process data somehow
};
(async () => {
console.log('start');
await mainstart();
console.log('end');
})();

How to make a transaction in cosmos DB with NodeJS

** Edited **
I am trying to insert data to Azure cosmos DB if its not exist but can't find a way to do it in one transaction.
the current way I'm doing it is by first getting it from cosmos and if it's throwing Not found exception then insert, otherwise update it.
the issue with that is that between the get and insert someone can push an object so this approach is unsafe.
code snippet:
static async fetchOrgConfigurationTableByQuery(orgId: string): Promise<OrgConfigurationTable> {
const { container } = await getContainer(Constants.CosmosRegional, Constants.OrgConfigurationTableName);
const orgConfigurations = await container.items.query(`SELECT * from c WHERE c.OrgId = "${orgId}"`).fetchAll();
const orgConfiguration = orgConfigurations.resources.find(() => true) as OrgConfigurationTable;
if (!orgConfiguration) {
return;
}
const orgConfigurationWithDefaults = new OrgConfigurationTable();
for (const [key] of Object.entries(orgConfiguration)) {
orgConfigurationWithDefaults[key] = orgConfiguration[key];
}
return orgConfigurationWithDefaults;
}
static async upsertOrgConfiguration(
orgConfiguration: OrgConfigurationTable,
failOnEtagMismatch = false
): Promise<OrgConfigurationTable> {
const requestOptions = getRequestOptions(orgConfiguration, failOnEtagMismatch);
const { container } = await getContainer(Constants.CosmosRegional, Constants.OrgConfigurationTableName);
const returnValue = await container.items.upsert<OrgConfigurationTable>(orgConfiguration, requestOptions);
return returnValue?.resource;
}
what I have:
let orgConfig: Cosmos.OrgConfigurationTable = null;
try {
orgConfig = await fetchOrgConfigurationTableByQuery(orgId);
} catch (error) {
if (!error instanceof Cosmos.OrgNotFoundException) {
log(error)
}
}
orgConfig = {
...orgConfig,
attribute: value,
...(!orgConfig && { Key: { Value: orgId } }),
};
await upsertOrgConfiguration(orgConfig)
what I want - to dont get before upsert:
let orgConfig = {
attribute: value,
Key: { Value: orgId },
};
await upsertOrgConfiguration(orgConfig) // if exist update, else insert - in single transuction.
or some way to do somthing like this:
transaction {
let orgConfig: Cosmos.OrgConfigurationTable = null;
try {
orgConfig = await fetchOrgConfigurationTableByQuery(orgId);
} catch (error) {
if (!error instanceof Cosmos.OrgNotFoundException) {
log(error)
}
}
await upsertOrgConfiguration(orgConfig)
}
commit
any ideas?

unable to get results from postgres in nodejs if callled from nested function

I have DB class which has public static query method. There can be multiple connection pools saved in _coonection_pools so I am creating a random number then get the pool and execute the query . this is the code
static async query(queryInfo: Query): Promise<any> {
const query = `select * from ${queryInfo.name} ${queryInfo.arguments} as info;`;
try {
return this._queryReplicaDB(queryInfo);
} catch (err) {
console.log("err", err);
throw err;
}
}
static async _queryReplicaDB(query: Query): Promise<any> {
const randomNumber = Math.floor(Math.random() * this._connection_pools.length);
// get the pool using random number
const pool = this._connection_pools[randomNumber];
try {
const response = await pool.query(query);
return response.rows[0].info;
} catch {
let response;
// if replica db fails then try with other pools
for (let i = 0; i < this._connection_pools.length; i++) {
// iterrate through every pool
const pool = this._connection_pools[i];
try {
response = await pool.query(query);
break;
} catch {
console.log("Error in Pool index: ", i);
}
}
return response.rows[0].info;
}
}
This is return empty rows in the response array
but if instead of calling nested _queryReplicaDB object this works fine
static async query(queryInfo: Query): Promise<any> {
const query = `select * from ${queryInfo.name} ${queryInfo.arguments} as info;`;
try {
const response = await this._connection_pools[0].query(query);
return response.rows[0].info;
} catch (err) {
console.log("err", err);
throw err;
}
}
i have tried this._connection_pools[0] in the _queryReplicaDB as well but this does not work.
I tried random number thing directlty in the query method in this method this works.
what can be the issue?
In my case code was correct, its just I was passing the wrong object type. Instead of passing Query object
static async _queryReplicaDB(query: Query): Promise<any> {
this function should accept string type as the query
static async _queryReplicaDB(query: string): Promise<any> {

NodeJS MongoDB how to get the data outside of the function

This is my Code. In the mongodb Documentation it says, that I should replace the console.dir inside the forEach, with a callback to access the individual elements. I have tried everything in my knowledge, but got no success.
I need the data outside of the function, so I can loop through it, and get the emails of the users.
const client = new MongoClient(url);
async function run() {
try {
await client.connect();
const database = client.db(dbMongo);
const users = database.collection("users");
const query = {};
const cursor = users.find();
if ((await cursor.count()) === 0) {
console.log("No documents found!");
}
await cursor.forEach(console.dir);
} finally {
recipients = "";
await client.close();
}
}
run().catch(console.dir);
Here's an example of how to make the run function return the results:
const { MongoClient } = require("mongodb");
// Replace the uri string with your MongoDB deployment's connection string.
const uri = "mongodb://localhost?writeConcern=majority";
const client = new MongoClient(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
async function run() {
let docs = [] //<- Declare a variable to hold your docs
try {
await client.connect();
const database = client.db(dbMongo);
const users = database.collection("users");
const query = {};
const cursor = users.find();
if ((await cursor.count()) === 0) {
console.log("No documents found!");
}
await cursor.forEach((doc)=>docs.push(doc.email));//<-Callback to add what you need into the docs array
} finally {
await client.close();
return docs //<- return the docs
}
}
//Wrap call in iife to use await here
(async()=>{
let results = await run().catch(console.dir);
console.log('----- Logging Results -------')
console.log(results)
})()
Use a global variable array to push the data inside cursor.forEach
cursor.forEach(doc=>{ Global_Array.push(doc); })
Use this Array elsewhere in the code
Remember to await run() and use the global array after run is called

NodeJS, get return value from async await

I have an async await function that uses mongoose:
const createModelB = async (id) => {
try {
let user = await User.findOne({id: id});
if (user) {
let modelB = new ModelB({ user_id: user.id });
modelB = await scrum.save();
return modelB;
}
return null;
} catch (err) {
console.error(err);
}
return null;
};
Now then I'm calling this function from somewhere else:
let modelB = createModelB(123);
console.log(modelB);
Instead of outputting Models fields, console returns to me this:
Promise {<pending>}
What did I miss?
I think it will be
const createModelBInstance = async () => {
let modelB = await createModelB(123);
console.log(modelB);
}
createModelBInstance();
as long as async functions are returning values you have to call an await on them, and should be wrapped inside another async function.

Resources