Postgresql driver for the multi-database abstraction#3057
Postgresql driver for the multi-database abstraction#3057rustyrussell merged 22 commits intoElementsProject:masterfrom
Conversation
220ba45 to
ada5925
Compare
|
Turns out |
031b30c to
7c5d5de
Compare
|
This should be ready for review. The tests now pass correctly and I checked with Ping @rustyrussell and @ZmnSCPxj 😉 |
rustyrussell
left a comment
There was a problem hiding this comment.
Minor changes only, this is both a nice addition and a good clean out of some nastiness!
doc/lightningd-config.5
Outdated
| .SH AUTHOR | ||
|
|
||
| Rusty Russell <\fIrusty@rustcorp.com.au\fR> wrote this man page, and | ||
| Rusty Russell <\fBNone\fR (\fIrusty@rustcorp.com.au\fR)> wrote this man page, and |
There was a problem hiding this comment.
This kind of gratuitous format change seems to keep happening: do we need to fixup the md files somehow?
There was a problem hiding this comment.
Yep, it seems to be an issue with mrkd which assumes we have link text even on bare links. We can fix this in mrkd since @darosior has a fork, either by ignoring non-marked up links of default them to the link URL.
There was a problem hiding this comment.
PR up: darosior/mrkd#1
Will switch the requirements.txt file to use that branch once the PR gets merged.
There was a problem hiding this comment.
Nice, hadn' noticed that refi64 is still merging PRs :-)
| db_fatal("Could not parse the wallet DSN: %s", db->filename); | ||
|
|
||
| /* Strip the scheme from the dsn. */ | ||
| filename = db->filename + strlen("sqlite3://"); |
There was a problem hiding this comment.
Wonder if we could use a strafter helper which did this dance for us?
There was a problem hiding this comment.
Sounds good, would you like 2 versions: one returning a pointer into the string and the other copying into a separate tallocated string?
I'll fix this up in my cleanups PR at the end of the week.
| * generate identical names to work correctly. | ||
| */ | ||
| #define SQL(x) NAMED_SQL( __FILE__ ":" stringify(__LINE__) ":" stringify(__COUNTER__), x) | ||
| #define SQL(x) NAMED_SQL( __FILE__ ":" stringify(__COUNTER__), x) |
There was a problem hiding this comment.
Hmm, I guess we can assume all compilers support __COUNTER__. Should be fine.
There was a problem hiding this comment.
Yeah, the __LINE__ macro didn't work across multiple compilers. I'm now simply using the raw string of the original query, so we could drop this again (or reinstate the __LINE__ as a debugging hint when working on the rewriter).
I was hoping to shortcut the string comparison a bit by having shorter names, rather than comparing the full statements which are longer, but generating identifiers proved to be more difficult.
| * height. This may introduce a block with NULL height if we didn't have any | ||
| * blocks, remove that in the next. */ | ||
| {SQL("INSERT OR IGNORE INTO blocks (height) VALUES ((SELECT " | ||
| {SQL("INSERT INTO blocks (height) VALUES ((SELECT " |
There was a problem hiding this comment.
Both should be happy with INSERT .... ON CONFLICT IGNORE
There was a problem hiding this comment.
Nope, postgres doesn't like IGNORE, it calls it DO NOTHING. How I love these little nuggets 😉 I'll add a rewrite rule for it
There was a problem hiding this comment.
Nope, turns out sqlite3 doesn't support ON CONFLICT statements up until version 3.24 so this is not portable in any case, unless we want to make for a really weird rewrite-rule that transforms a INSERT OR IGNORE into a INSERT INTO ...loads of other stuff... ON CONFLICT DO NOTHING.
In this case I don't think it's worth it, since it predates v0.6 😉
There was a problem hiding this comment.
We should make this explicit then, I think (separate patch at the end). Remove early migrations, complain if db is before that, and replace them with nicer db statements which are only used on initializing the db.
There was a problem hiding this comment.
Yech, that's admitting defeat! I'll give it another go.
wallet/db.c
Outdated
| {SQL("ALTER TABLE invoices ADD paid_timestamp INTEGER;"), NULL}, | ||
| {SQL("UPDATE invoices" | ||
| " SET paid_timestamp = strftime('%s', 'now')" | ||
| " SET paid_timestamp = 1" |
There was a problem hiding this comment.
Fair enough, though we could argue this belongs in the postgres translation, this is good enough since it will Never Happen.
There was a problem hiding this comment.
Yep, that was my reason for just setting a dummy value, nobody should have a DB this old, and nothing bad happens if it goes wrong.
There was a problem hiding this comment.
Just to make sure I went and looked up where the migration was introduced: a88c73a
This predates v0.6 as well.
wallet/wallet.c
Outdated
| /* Some databases return a NULL result if there are no | ||
| * operands for the SUM(). This is the case for example for | ||
| * postgres. */ | ||
| total = AMOUNT_MSAT(0); |
There was a problem hiding this comment.
COALESCE(SUM(in_msatoshi - out_msatoshi), 0) is Sequelly I think?
There was a problem hiding this comment.
Yeah, problem is COALESCE doesn't result in a BIGINT, so a CAST(COALESCE(field, 0) AS BIGINT) is required.
|
|
||
| stmt = db_prepare_v2(w->db, SQL("INSERT INTO blocks " | ||
| "(height, hash, prev_hash) " | ||
| "VALUES (?, ?, ?);")); |
There was a problem hiding this comment.
ON CONFLICT IGNORE? Or was the "OR IGNORE" unnecessary?
There was a problem hiding this comment.
Turns out that given the above check for wallet_have_block and being in a DB transaction this should be unnecessary.
| if (!db_column_is_null(stmt, 0)) | ||
| blockheight = db_column_int(stmt, 0); | ||
| else | ||
| blockheight = 0; |
There was a problem hiding this comment.
Maybe a db_column_int_or_null() helper for this pattern?
There was a problem hiding this comment.
I had a branch with db_column_type_or_default() at some point which I may dig up again if this happens alot.
| stmt = db_prepare_v2(w->db, | ||
| SQL("INSERT OR REPLACE INTO forwarded_payments (" | ||
| SQL("INSERT INTO forwarded_payments (" | ||
| " in_htlc_id" |
There was a problem hiding this comment.
Upsert went into postgres 9.5, and we seem to be insisting on postgres 10 anyway?
There was a problem hiding this comment.
Yep, but the ON CONFLICT(field) DO UPDATE syntax is not in sqlite3 below 3.24, so we can't use it. Splitting it into two statements was the only really portable way I found.
There was a problem hiding this comment.
Yech. Nobody cares about compatibility, do they?!?
wallet/wallet.c
Outdated
|
|
||
| stmt = db_prepare_v2(w->db, SQL("SELECT" | ||
| " SUM(in_msatoshi - out_msatoshi) " | ||
| " CAST(SUM(in_msatoshi - out_msatoshi) AS BIGINT)" |
There was a problem hiding this comment.
Hard to find exaclty, but I think CAST(NULL) gives 0 as expected. Might deserve a comment though?
There was a problem hiding this comment.
The combination of CAST(COALESCE(field, 0) AS BIGINT) should take care of this.
There was a problem hiding this comment.
Just noticed that the COALESCE call comes in in a later commit so not touching it here.
7c5d5de to
a997888
Compare
|
I addressed most of the things that @rustyrussell commented on
For easier review I created a range-diff that just contains the changes between 7c5d5de to a997888 😉 Couldn't find a way to share the colored output of range-diff which is way easier to read. If you'd like to reconstruct it this is the command: |
|
Ack a997888 I would like to see that db migration cleanup though. With a message for old dbs that they have to migrate to 0.7+ first. MINIMUM FIX is to simply refuse migrations if old db version is below 85 (v0.6.0). |
If we have the client library for postgres configure will define HAVE_POSTGRES the same way it already handled libsqlite3 an we start linking against it. Signed-off-by: Christian Decker <decker.christian@gmail.com>
We will soon have a postgres backend as well, so we need a way to control the postgres process and to provision DBs to the nodes. The two interfaces are the dsn that we pass to the node, and the python query interface needed to query from tests. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Will be demuxed into starting the selected DB backend in one of the next commits. Defaults to the old database location. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
We used to do some of the setup work in db.c, which is now free of any sqlite3-specific code. In addition we also switch over to fully qualified DSNs to specify the location of the wallet. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This is dangerous but needed since postgres is not as forgiving about unsatisfied foreign key constraints even while in a DB transaction. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Using a generated identifier with filename and line proved to be brittle since compilers assign the __LINE__ macro differently on multi-line macro invocations. Signed-off-by: Christian Decker <decker.christian@gmail.com>
a997888 to
ccdd599
Compare
Needed to change a couple of migrations. The changes are mostly innocuous: - changing BLOB to TEXT for short_channel_ids which is the correct type anyway, and sqlite3 treats them the same anyway. - Use `int` for version since the byte representation is checked by postgres. - Change anything that is INT, but will be bound to u64 to BIGINT (again postgres checks these more carefully than sqlite3). Two migrations were replaced with dummy values, since they are buried deep enough, and I found no portable way of expressing `strftime()` and `INSERT OR IGNORE`. Signed-off-by: Christian Decker <decker.christian@gmail.com>
Signed-off-by: Christian Decker <decker.christian@gmail.com>
The first ever query to check if the version DB exists may fail. We allow this, but we need to restart the DB transaction since postgres fails the current transaction and rolls back any changes. This just commits (and fails) and starts a new transaction so the rest of the migration can continue. Signed-off-by: Christian Decker <decker.christian@gmail.com>
We were doing exact matches before, but prefix is sufficient. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This was weird right from the start, so we just split the table into integers and blobs, so each column has a well-defined format. It is also required for postgres not to cry about explicit casts in the `paramTypes` array. Signed-off-by: Christian Decker <decker.christian@gmail.com>
We now have an abstract rewriter that will perform some common extractions and replacements (type replacement for example), that can then be customized in derived classes. Signed-off-by: Christian Decker <decker.christian@gmail.com>
sqlite3 doesn't really do any validation whatsoever, and there is no difference between 64bit and 32bit numbers. Posgtres on the other hand gets very upset if the size doesn't match. This commit swaps out handwavy types with the ones that should be there :-) Signed-off-by: Christian Decker <decker.christian@gmail.com>
sqlite3 will just report 0 for anything that it thinks should be numeric, or is accessed using a numeric accessor. Postgres does not, so we need to check for is_null before trying to read it. Signed-off-by: Christian Decker <decker.christian@gmail.com>
This was already done in `db_step` but `db_count_changes` and `db_last_insert_id` also rely on the statement being executed. Furthermore we now check that the statement was executed before freeing it, so it can't happen that we dispose of a statement we meant to execute but forgot. The combination of these could be used to replace the pending_statement tracking based on lists, since we now make sure to execute all statements and we use the memleak checker to make sure we don't keep a statement in memory. Signed-off-by: Christian Decker <decker.christian@gmail.com>
sqlite3 was forgiving, postgres isn't, so let's make sure we use the strictest field type possible, relaxing when rewriting. The commit consists just of the following mapping - INTEGER -> BIGSERIAL if it is the primary key - INTEGER -> BIGINT if it is an amount or a reference to a primary key Signed-off-by: Christian Decker <decker.christian@gmail.com>
The DB field type has to match the size of the accessor-type, and we had to split the `REPLACE INTO` and `INSERT INTO OR IGNORE` queries into two queries (update and insert if not updated) since there is no portable UPSERT operation, but impact should be minimal. Signed-off-by: Christian Decker <decker.christian@gmail.com>
These will not work since they touch the DB file itself. Signed-off-by: Christian Decker <decker.christian@gmail.com>
The short_channel_id is already in text format, no need to hexlify it :-) Signed-off-by: Christian Decker <decker.christian@gmail.com>
This replaces the hard-coded path to the `postgres` and `initdb` binaries with a slightly more flexible search. It'll pick the newest version installed. Signed-off-by: Christian Decker <decker.christian@gmail.com>
The test was implicitly relying on us selecting the larger output and then not touching the smaller, leaving it there for the final `withdraw` to claim. This ordering of UTXOs is not guaranteed, and in particular can fail when switching DB backends. To stabilize we just need to make sure to select the change output as well.
ccdd599 to
951193e
Compare
|
Ok, I looked a little bit harder and it turns out rewriting I rebased and squashed right away, but here's the range-diff for between the reviewed version and the current head: Notice that whenever possible I chose the standardized syntax over the sqlite3 specific, and then rewrote the sqlite3 version, hopefully that reduces the need for many rewrite rules for future DBs. |
This is the counterpart to #2924. It implements the postgresql driver for
lightningd, the changes to the query rewriting script and the changes to the testing framework to select the DB to test against.The tests can be run, if you have postgresql installed, using the
TEST_DB_PROVIDER=postgresenvironment variable. The tests pass, with one exceptiontest_reserve_enforcement, which I am still investigating, and the tests are a bit flaky when running in parallel.Sadly I had to modify the migrations in order to add the information that
sqlite3just glossed over (field size, foreign key enforcement in a transaction, autoincrement primary keys). Changing the migrations is something that I warned against back when I created the migration system, however it is safe to do here since we only reorder one single migration pair (since postgres requires the dependency tablepeersto be created before the dependent tablechannels, safe because it was part of the same git commit back in the day and there is no way someone has a DB with one table but not the other), and some changes in types.The type changes are limited to the
INTEGERchanging toBIGSERIALif it is the primary key, orBIGINTif it is backing au64. There are one or two occurrences where I had to change fromTEXTtoBLOB(which then gets translated toBYTEAfor postgres), sinceTEXTrequires the contents to be valid UTF-8 strings, which public keys are not.Finally, I was planning on using
sqlparseto make the statement rewriting context aware, and safer than simple query-replace, but I found it rather cumbersome to work with, and we are just replacing a couple of types and placeholders anyway, so I ripped that dependency out again in this PR.Some things that I will try to address before removing the draft status:
initdband thepostgresbinaries instead of hardcoding the pathtest_reserve_enforcementvalgrindwith postgres to ensure that everything is still memory-safe.Looking forward to the feedback 😉