How can I track a change to sqlite3 in golang? - sqlite

everybody. I use the library "github.com/mattn/go-sqlite3" for working with the database.
I need to track the changes made to the database and after these changes are complete, execute some code.
I need to track the changes that another process makes to this database..
For example, there is a Products table with the name and id fields I want to get a notification after the name field has been changed
How can I do this? any solution Thanks
sqlite3conn := []*sqlite3.SQLiteConn{}
sql.Register("sqlite3_with_hook_example",
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
sqlite3conn = append(sqlite3conn, conn)
conn.RegisterUpdateHook(func(op int, db string, table string, rowid int64) {
switch op {
case sqlite3.SQLITE_INSERT:
log.Println("Notified of insert on db - ", db, "table:", table, "rowid:", rowid)
}
})
return nil
},
})
This code only tracks the changes that the Go script makes, if I make an insert from the console, it doesn't work

Related

How to write this conditional request correctly MariaDB

I need to check if a book rating for specific book from specific person exists.
If it does update it, if it doesnt create it.
I am getting a whole bunch of wrong errors for 9th 10th.... 12th parameter missing while I count only 8
My mariaDB version is 10.5.8-MariaDB.
My code:
const createBookRate = async (userId, bookId, rate) => {
const sql = `
SELECT IF(EXISTS( SELECT * from rates WHERE rates.users_id=? AND rates.books_id=? ),
UPDATE rates SET rates.rate=? WHERE rates.users_id=? AND rates.books_id=?,
INSERT INTO rates(users_id, books_id, rate))
VALUE (?,?,?,?,?,?,?,?);
`
const { insertId } = await pool.query(sql, [userId, bookId, rate, userId, bookId, userId, bookId, rate])
const rateEntry = await getBookRate(insertId)
return rateEntry
}
You cannot perform an UPDATE or an INSERT inside the IF clause of a SELECT statement, those must be performed separately.
To perform this in a safe manner, use a transaction and first lock the selected row with SELECT ... FOR UPDATE, then either UPDATE or INSERT it and finally COMMIT the transaction.
If the table has a primary key, you can use INSERT ... ON DUPLICATE KEY UPDATE to either insert the row or update it, depending on whether it exists or not. This allows everything to be done in one step without having to first select the affected rows.

TypeOrm QueryBuilder dynamic auto delete function with crons job not working

Token for email verification is created with User registration and needs to be deleted from database within 24 hours with crons job help. In a delete function using query builder, token gets deleted only if date value is manually provided in form of string: {delDate: "2021-02-08T17:59:48.485Z" }. Here, all tokens with date before or equal 2021-02-08 get deleted, and is working fine. But thats a static input, manually put in hard code!
Since this must be a dynamic input, I set up variable 'delTime',which stores current date minus 24 hrs in it, but it seems .where condition will not take a variable as value, and will not delete, as: {delDate: deltime}. In fact, 'delDate' consoles exactly the info I need, but it will only work in form of string.
There is a ton of content online teaching how to delete stuff with a static value in typeorm querybuilder, but so hard to find with dynamic values....
How else can I make this work in a dynamic way ?
async delete(req: Request, res: Response){
try {
const tokenRepository = getRepository(Token);
var delTime = new Date();
delTime.setDate( delTime.getDate() - 1 );
console.log(delTime) //consoles 24 hors ago
await tokenRepository
.createQueryBuilder()
.delete()
.from(Token)
.where("tokenDate <= :deleteTime", { deleteTime: delTime})//value dynamically stored in variable does not delete
//.where("tokenDate <= :deleteTime", { deleteTime: "2021-02-08T18:01:10.489Z"})//static hard code value deletes
//.where("tokenDate <= :delTime", { delTime})//Variable put this way will not work either...
.execute();
} catch (error) {
res.status(404).send("Tokens not found");
return;
}
res.status(200).send('Tokens deleted successfuly');
}
Your parameterized query looks correct.
Switch on TypeOrm Query Logging to see the generated SQL, maybe you will be able to see what's going wrong. Paste the generated SQL into the SQL query console to debug.
Alternatively you can write your delete query without parameters and let Sqlite calculate current date -1 day:
.where("tokenDate <= DateTime('Now', '-1 Day'"); // for Sqlite
Note 1: Sqlite DateTime('Now') uses UTC time, so your tokenDate should use UTC also.
Note 2: This syntax is specific to Sqlite, you can do the same in other databases but the syntax is different, e.g. Microsoft SQL Server:
.where("tokenDate <= DATEADD(day, -1, SYSUTCDATETIME()); // for MS SQL Server

Firestore transactions - How to Add only when key does not exist. Never wish to update

I have a collection where I insert a document. I need to insert a document with a particular key only if no one has already added it before. If key exists and I need to update using transactions - that work great. But here I need to ensure no document with that keys exists and then insert it. Please do not discuss how to update existing record with firestore transactions - that works great - and that is not my question. My question is how to ensure and CHECK the document does not exist in the collection, and only then ADD it. And Make sure between my CHECK and ADD - no one grabbed the opportunity and added the document with that key. In that case I will end up updating the document that was just added by someone. Because seems like firestore transactions do not lock non-existing documents - or do they? What is the solution for this issue?
// Checking if document exists - if it does updating some field (not important)
ref := clientdb.Collection("mycollection").Doc("12345")
err = clientdb.RunTransaction(context.Background(), func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(ref) // tx.Get, NOT ref.Get!
if err != nil {
return err
}
count1, err := doc.DataAt("querycount")
if err != nil {
return err
}
return tx.Set(ref, map[string]interface{}{
"querycount": count1.(int64) + 1,
}, firestore.MergeAll)
})
/* Important part is here */
/* If someone inserts the document with same key here, I will end up updating it below */
/* I wish to guarantee no one inserts document with that key "12345" */
/* THE TIME HERE is crucial, no one should be able to add the key here (say a different session/thread that preempted my OS thread at this location or if firestore inserted someone else document using different session) */
if err != nil {
// Inserting document here - do not wish to update if someone already inserted after the above check
_, err = clientdb.Collection("mycollection").Doc("12345").Set(context.Background(), map[string]interface{}{
"field1": field1,
"querycount": 0,
})
}
How to ensure, I am not updating someone else's document (or my own document that was added just microseconds ago with multiple button clicks, or two people clicked at the same time to add and one of them grabbed it and added - right between the check and the actual insert/add command of the second person.) More important I need to add only if no one has already added. Do not wish to touch/update document someone else added (or touch my own document if I added it before - say multiple button clicks and sending the request again and again). Thanks!
Call DocumentRef.Create to create a new document. The Create method fails if a document with the same id exists.
See also Transaction.Create and WriteBatch.Create.
Use CollectionRef.NewDoc to create a unique document id.
Your doc variable is a DocumentSnapshot. You can use its Exists() method to check if the document exists, and use that to decide what to do in your transaction as a result.
Thank you, It worked with Firestore Create command. Adding it here.
current_id_try = "12345"
... loop
_, err := clientdb.Collection("mycollection").Doc(current_id_try).Create(context.Background(), map[string]interface{}{
"field1": field1,
"querycount": 0,
})
if err != nil {
// Failed to insert, so return to the next iteration of the for loop to try again.
current_id_try = some_other_value[i]
continue
} ...break on max tries...
.. end loop

Trouble inserting data into sqlite database with JDBC

This is what my code currently looks like:
import java.sql.*
import java.sql.SQLException
class SqliteDB {
val conn = DriverManager.getConnection("jdbc:sqlite:cs2820-database.db")
fun createUser123(userID: String, password: String, adminStatus: String) {
val statement = conn.prepareStatement("INSERT INTO Users(id,pass,admin) VALUES(?,?,?)")
statement.setString(1, userID)
statement.setString(2,password)
statement.setString(3,adminStatus)
println("123")
statement.executeUpdate()
conn.commit()
println("User Created")
}
// create a user
fun createUser(userID: String, password: String, adminStatus: String) {
println("inside createUser")
val sql = "INSERT INTO Users(id,pass,admin) VALUES(?,?,?)"
Class.forName("org.sqlite.JDBC")
try {
conn.use { conn ->
conn.prepareStatement(sql).use { pstmt ->
pstmt.setString(1, userID)
pstmt.setString(2, password)
pstmt.setString(3, adminStatus)
pstmt.executeUpdate()
//conn.commit()
pstmt.close()
}
}
} catch (e: SQLException) {
println(e.message)
}
}
fun main(args: Array<String>) {
val db = SqliteDB()
db.createUser("Jim", "password", "false")
}
I have tested two different createUser methods and most everything I have tried will return the error [SQLITE_BUSY] The database file is locked (database is locked). I have several other methods within the same SqliteDB class that query data ("SELECT") that all work correctly, but every time I try to perform any kind of update of information I am given the same error. I am at a loss for what to do at this point having searched many different forums and posts about syntax and such.
The full stacktrace is as follows:
Exception in thread "main" org.sqlite.SQLiteException: [SQLITE_BUSY] The database file is locked (database is locked)
at org.sqlite.core.DB.newSQLException(DB.java:909)
at org.sqlite.core.DB.newSQLException(DB.java:921)
at org.sqlite.core.DB.execute(DB.java:822)
at org.sqlite.core.DB.executeUpdate(DB.java:863)
at org.sqlite.jdbc3.JDBC3PreparedStatement.executeUpdate(JDBC3PreparedStatement.java:99)
at SqliteDB.createUser(SqliteDB.kt:50)
at SqliteDBKt.main(SqliteDB.kt:122)
I don't believe the issue is with an open connection as the error seems to occur as soon as I try to execute the update to the User table. The insert seems to hit some sort of loop of some kind at the execution stage.
EDIT: Something else I noticed, is that when attempting to create a new user with a primary key (userID) that already exists, I am given a uniqueness error, suggesting the update is going thru and realizing the userID is already in the table; however, there is still the issue with the INSERT creating a new row in the table. I'm just not sure how to go about debugging that specific issue.
So this has been quite an issue for me for the past week or so and it looks like I have been able to figure out the issue.
I am aware that you have to make sure to close any connections to the database before opening a new one to avoid locks and any other similar issues. What I did not realize is that I have been using a program in IntelliJ called "DB Browser". This plugin creates a UI that much more easily allows you to access and change any aspect of the database you want without using actual SQL commands. What I didn't realize is this plugin takes up the only writeable connection the SQLite and JDBC allow to the database.
So, after deleting the connection to the database through the DB Browser plugin, all of my functions are working properly.

Fire an action once a transaction ends

I'm using Stephen Celis iOS lib for handling SQLite3 databases, here is the github link.
Taking the example on the git :
try db.transaction {
let rowid = try db.run(users.insert(email <- "betty#icloud.com"))
try db.run(users.insert(email <- "cathy#icloud.com", managerId <- rowid))
}
// BEGIN DEFERRED TRANSACTION
// INSERT INTO "users" ("email") VALUES ('betty#icloud.com')
// INSERT INTO "users" ("email", "manager_id") VALUES ('cathy#icloud.com', 2)
// COMMIT TRANSACTION
I tried to implement the commitHook block but it is fired for each insert. I'd like to fire an action only when all the requests are sent :-D
What should I do ?
Cheers
Edit :
Here is how I implemented the commit hook.
for bay in list{
try! self.themanager.db.transaction {
try! self.themanager.db.run(self.themanager.bays.insert(
//insert values
))
self.themanager.db.commitHook({
print("end commit hook")
})
}
}
Maybe it's related to my main loop :/
From SQLite TRIGGER docs:
At this time SQLite supports only FOR EACH ROW triggers, not FOR EACH STATEMENT triggers. Hence explicitly specifying FOR EACH ROW is optional. FOR EACH ROW implies that the SQL statements specified in the trigger may be executed (depending on the WHEN clause) for each database row being inserted, updated or deleted by the statement causing the trigger to fire.
Commit hooks work like triggers. Unfortunately, the "FOR EACH STATEMENT" behavior is not supported yet.

Resources