-
-
Notifications
You must be signed in to change notification settings - Fork 818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add support for pre-update hook #702
Conversation
Nice! Maybe getting the column names is not so slow because the fist database page (where schemas are stored) may be cached in memory. I use this code below to get the columns names. Note that it does not use sqlite3_stmt * get_table_columns(sqlite3 *db, const char *table) {
sqlite3_stmt *stmt=0;
char *sql=0;
sql = sqlite3_mprintf("SELECT * FROM %s WHERE 1=0", table);
if (sql == 0) goto loc_exit;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
sqlite3_finalize(stmt);
stmt=0;
}
loc_exit:
if (sql) sqlite3_free(sql);
return stmt;
} It returns the prepared statement with the columns names. Then we can use |
Yeah, after a bunch of research I found someone else suggesting that as well. Still, I didn't know how much work
Does sqlite3 manually do this? Keep some sort of cache? Or do you mean we will most likely not hit the disk as it will already be cached by the OS? |
Looks like emitting an object with column names is significantly slower:
Goes from an average of ~.36s to ~.92. My test case is simple: function onUpdate(type, db, table, oldValues, newValues) {
}
db.addListener("preupdate", onUpdate);
db.run('UPDATE transactions SET description="foo" WHERE amount < 40000', function(err) {
}); This is run against a database containing thousands of rows, and the query affecting 21568 of them. My first guess was that maybe creating JS objects are slower than arrays, but that shouldn't be true so there isn't much extra JS allocation. Must be the process of preparing a statement and getting the column names. |
This is an interesting result. You can just retrieve the column names without creating the javascript object, just to check the speed. And yes, the SQLite has a page cache but I guess it is used for modified pages to be kept on memory before a transaction is commited. I am not sure if the same page cache is used when running a second SQL query. I can test it. |
Maybe it's because the hook function is being called for each modified row. What is your goal with this code? Synchronization? |
Yes, which is why I didn't set it up that way in the first place. It's an advanced feature so users are expected to manage the column names themselves.
I'm developing a specialized app that needs to know when things change. Hard to explain without going into the details of the app. I'm expecting to maintain a fork for now and if there's interest I can help merge this in, but as said earlier I wouldn't be surprised if the maintainers didn't want this right now. |
So there are 2 bad things still going on with this PR:
I tried compiling a version of sqlite without mutexes just in case it was a race condition, but that didn't help. Tried a bunch of other stuff too. I think it only happens when it fires the hook a lot of times (here, about ~20000). Any guidance for how to debug this would be great. |
Figured it out! These lines are problematic: https://github.com/mapbox/node-sqlite3/blob/master/src/async.h#L65-L69 If sending thousands of events, node just never shuts down. |
`uv_ref()` can't be called from the non-event-loop thread. Furthermore, it is not necessary in `async.h`, because open `uv_async_t` is already holding the event loop from exiting. See: TryGhost#702
This is the latest code that works pretty well for me. Got some help with the race condition and it is solved in #705 (thanks!). The last issue is that the preupdate hook isn't guaranteed to be done firing before the I think this is a problem, but not big enough to block me. I'll have to fix it at some point, so if anyone has pointers, that'd be great. Otherwise I'll fix it later. |
Found this thread looking for this feature, any reason why this never got merged? |
Hey @jlongster, would you be able to rebase the PR? 🙂 |
I use https://github.com/WiseLibs/better-sqlite3 now and I don't need this anymore so I don't have time to continue to work on this, sorry... (6 years later!) |
No worries 🙂 I'm going to close this PR for now, but I may look into implementing this in the near future |
I'm experimenting with adding support for the preupdate hook. This hook is fired before any
INSERT
,UPDATE
, orDELETE
operations are committed to the database, and gives you access to the old values and the new values. I don't know if the current maintainers are interested in having this, but regardless I'd like some feedback on implementing it. If you all do not want to merge this I can keep this patch locally.This allows you to do this:
The output is:
The first big change is I need to update to sqlite 3.14. The preupdate hook does not exist in the version current bundled into node-sqlite.
Then, I needed to compile it with the
SQLITE_ENABLE_PREUPDATE_HOOK
option.The rest of the code mostly follows the conventions of the
profile
andtrace
events. However, the preupdate hook is a bit more work. Because it gives you access to changed rows, I need to construct twoRow
values that I can emit with the event. A few problems with this:Row
andValues
is not available indatabase.cc
as they are defined instatement.h
. I needed to move them intodatabase.h
but this is probably not desirable. A better approach would be to create a new header file that both can include.RowToJS
fromstatement.cc
. This also means any of theValues::Field
objects need a "placeholder" name that I don't actually use. Again, there's probably a better way to do this.The user is expected to query the column names on app startup, as there would surely be a big perf penalty by querying them on every preupdate event.
I'm looking for general feedback; is this the right approach? Any obvious memory leaks, etc?