Look ma, multiple DB support (Part I of the DB saga)#2924
Look ma, multiple DB support (Part I of the DB saga)#2924rustyrussell merged 32 commits intoElementsProject:masterfrom
Conversation
921da08 to
9c845d1
Compare
|
very cool |
So we now have our own SQL variant? Which is not to say it is a bad solution, mostly a gripe. I encountered this kind of issue before (GLSL shaders), and sometimes this is the only solution you can come up with without going whole hog and creating your own language.
C is an OOP language (for some definition of "is"). It is just that you have to implement OO yourself. See the shenanigans that gtk gets up to in its C code, for example. |
it's an interesting approach, I don't feel that strongly about it yet. the alternative being programming to some ORM which is never much fun. As long as it doesn't require that much hacking to get postgresql working, then replication will be much easier. |
rustyrussell
left a comment
There was a problem hiding this comment.
I think xgettext -kSQL makes this problem much simpler, and it's a tool which already exists for this purpose.
The resulting .po file format is trivial to parse, or we could literally use dgettext here too, but this would be a little weird if we ever support real localization of strings.
wallet/db.c
Outdated
|
|
||
| /* Since these queries will be treated as read-only they need to start | ||
| * with "SELECT" and have no side-effects. */ | ||
| assert(strncmp(query, "SELECT", 6) == 0); |
There was a problem hiding this comment.
We have 'strstarts()' for this BTW.
| * This macro is used to annotate SQL queries that might need rewriting for | ||
| * different SQL dialects. It is used both as a marker for the query | ||
| * extraction logic in devtools/sql-rewrite.py to identify queries, as well as | ||
| * a way to swap out the query text with it's name so that the query execution |
| "sqlite3": Sqlite3Rewriter(), | ||
| } | ||
|
|
||
| template = Template("""#ifndef LIGHTNINGD_WALLET_GEN_DB_${f.upper()} |
There was a problem hiding this comment.
put template in external file?
wallet/Makefile
Outdated
| wallet/db_sqlite3.c: wallet/gen_db_sqlite3.c | ||
|
|
||
| wallet/gen_db_sqlite3.c: devtools/sql-rewrite.py wallet/db.c wallet/wallet.c wallet/test/run-db.c | ||
| LD_LIBRARY_PATH=${LIBCLANG_PATH} devtools/sql-rewrite.py -f sqlite3 > wallet/gen_db_sqlite3.c |
There was a problem hiding this comment.
I believe you can use $@ as the destination/target filename here, instead of spelling it out again.
wallet/db.c
Outdated
| db->filename = tal_strdup(db, filename); | ||
| db->sql = sql; | ||
|
|
||
| for (size_t i=0; i<num_configs; i++) |
There was a problem hiding this comment.
spacing here is very Go-like >.<
There was a problem hiding this comment.
sometimesyoudon'twantspacesandit'sstillperfectlyreadable.
But maybe not here :)
| struct db_query *queries; | ||
| size_t num_queries; | ||
| /* Is this a read-only query? If it is there's no need to tell plugins | ||
| * about it. */ |
wallet/db_sqlite3.c
Outdated
| char *errmsg; | ||
| err = sqlite3_exec(db->conn, "COMMIT;", NULL, NULL, &errmsg); | ||
| if (err != SQLITE_OK) | ||
| fatal("Failed to begin a transaction: %s", errmsg); |
| } | ||
| } | ||
|
|
||
| if (err != SQLITE_OK) { |
There was a problem hiding this comment.
it's a bit weird semantically to be checking if the 'err' is OK. Consider renaming to result?
| } | ||
|
|
||
| err = sqlite3_step(stmt->inner_stmt); | ||
| if (err != SQLITE_DONE) { |
There was a problem hiding this comment.
same as previous comment.
There was a problem hiding this comment.
I pretty much copied this from the old code, and there I had gotten it from the sqlite3 example code :-)
fe79c77 to
b2a5434
Compare
2fb3e94 to
19e9459
Compare
|
Ok, I think this is now good to go. I went ahead and migrated all the places we used the legacy implementation with the new, indirect, implementation and everything seems to be working ok. I simplified the interface for querying a bit, removed a lot of redundancy and implemented the binding and column accessor functions to use the abstraction layer as well. I left the migration commits unsquashed for now so they are a bit less daunting, but once the PR has the required ACKs I'll squash them into a single commit. |
|
@cdecker I assume you squash the fixups now that it's ready to review? |
Keeping the migration fixups separate for until the review is done in order to make the review less daunting, but I'll squash before merging. @bitcoin-bot should be able to infer that the squashed version and the unsquashed version are the same (same changes) and re-apply ACKs automatically :-) |
Signed-off-by: Christian Decker <decker.christian@gmail.com>
All drivers will have to reach into it, so put it in a place that is reachable from the drivers, along with all other definitions. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This gets rid of the two parallel execution paths of read-only and write queries, by explicitly stating with each query whether it is a read-only query, we only need to remember the ones marked as write queries. Signed-off-by: Christian Decker <decker.christian@gmail.com>
These functions implement the lookup of the query, and the dispatch to the DB-specific functions that do the actual heavy lifting. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This is the DB-specific counterpart to the previous commit. Signed-off-by: Christian Decker <decker.christian@gmail.com>
These do not require the ability to iterate over the result, hence they can be migrated already. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This is much more in line with the rest of our memory management. Signed-off-by: Christian Decker <decker.christian@gmail.com>
For some of the query methods in the next step we need to have an idea of whether the stmt was executed (db_step function) so let's track that explicitly. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This is the first step towards being able to extract information from query rows. Only the most basic types are exposed, the others will be built on top of these primitives. Signed-off-by: Christian Decker <decker.christian@gmail.com>
I was hoping to get rid of these by using "ON CONFLICT" upserts, however sqlite3 only started supporting them in version 3.24.0 which is newer than some of our deployment targets. Signed-off-by: Christian Decker <decker.christian@gmail.com>
These are used to do one-time initializations and wait for pending statements before closing. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This has a slight side-effect of removing the actual begin and commit statements from the `db_write` hooks, but they are mostly redundant anyway (no harm in grouping pre-init statements into one transaction, and we know that each post-init call is supposed to be wrapped anyway). Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
These are based on top of the basic column access functions, and act as a small type-safe wrapper, that also does a bit of validation. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This is likely the last part we need to completely encapsulate the part of the sqlite3 API that we were using. Like the `db_count_changes` call I decided to pass in the `struct db_stmt` since really they refer to the statement that was executed and not the db. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
We now have a much stronger consistency check from the combination of transaction wrapping, tal memory leak detection. Tramsaction wrapping ensures that each statement is executed before the transaction is committed. The commit is also driven by the `io_loop`, which means that it is no longer possible for us to have statements outside of transactions and transactions are guaranteed to commit at the round's end. By adding the tal-awareness we can also get a much better indication as to whether we have un-freed statements flying around, which we can test at the end of the round as well. Signed-off-by: Christian Decker <decker.christian@gmail.com>
It's better to let the driver decide when and how to expand. It can then report the expanded statement back to the dispatch through the `db_changes_add` function. Signed-off-by: Christian Decker <decker.christian@gmail.com>
We are about to delete all the `sqlite3`-specific code from `db.c` and this is one of the last uses of the old interface. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
Now that all the users are migrated to the abstraction layer we can remove the legacy implementation. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com> Suggested-by: Rusty Russell <@rustyrussell>
|
Ok, fixed the rebase issue I was having problems with yesterday. Ready for review @rustyrussell 😉 |
|
Ack 969f469 |
Data integrity and data safety is paramount for nodes in the Lighning Network
since loss of data, or use of outdated data can directly lead to loss of
funds. c-lightning currently stores all its information in a
sqlite3database, and offers the opportunity to synchronously replicate any changes to
other storages through the use of the
db_writehook.To further bolster our data resilience, and allow for a variety of more
enterprisey deployments it is desirable to allownode operators to switch out
the
sqlite3backend for something else. In this PR implement a DBabstraction framework that allows to configure other DBMSs as backends, and
future PRs will add concrete implementations of those drivers.
This PR consists of a couple of distinct things, but I wanted to make sure it
actually works before submitting a partial solution.
and runtime lookup of the rewritten queries.
support, enabling the abstraction over the specific APIs. The API is very
much inspired by
sqlite3but I made sure that it can be easily mapped toMySQL and Postgresql. In addition we have number of helper functionality to
bolt down some avenues for errors:
sqlite3s1-based bindings (which I found out were inspired by
printf). Thiscaused a couple of misunderstandings in the past.
placeholders without a binding will result in failing the execution).
and allow minimizing allocations at runtime).
yet).
sqlite3driver, now using the uniform API, andkeeping all
sqlite3-specific code in a separate file that is easilyconfigured out if the library is missing or incompatible.
Since the "S" in SQL is a marketing lie, and SQL statements are not portable
across DBMSs I had to come up with a method to write portable code. The
solution I went for consists of marking the SQL statements that are to be
extracted with two macros (
SQLandNAMED_SQL), which then allows a tool(
devtools/sql-rewrite.py) to extract them and rewrite them into variantsthat work for the specific DBMS that we are rewriting for. The queries are
then stored in a large array inside of
wallet/gen_db_${dbms}.cand looked upusing a unique name (autogenerated in the case of the
SQLmacro).When preparing a DB statement we look up the rewritten statement in the array
and initialize a
struct db_stmt, to which we can attach bindings. Whenexecuting we perform a number checks and run the query against the selected
DBMS.
The DB-specific implementation of the common API is in
wallet/db_${dbms}.cand adheres to a common set of functions defined in
wallet/db_common.h.wallet/db.citself only dispatches calls to theDB-specific implementations through the use of a
struct db_configinstance(yes, it is a virtual dispatch table, I implemented dynamic dispatch in a
non-OOP language...), but none of this should ever leak outside of the files
mentioned here, and it'll just look like a normal DB from outside.
Still to do for this PR:
db.c.wallet.c.sqlite3in the non-sqlite3-specific files.Once I have these missing pieces I will remove the draft status, so don't feel
forced to review this until I do, but feedback is always welcome 😉
Still to do for future PRs:
INSERT OR REPLACE INTO)