From 9a591277f5a3e2452c2ec3e075da2e5b1dc3b139 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 30 Jan 2023 16:54:18 +1030 Subject: [PATCH] plugins/sql: allow some simple functions. And document them! Signed-off-by: Rusty Russell --- doc/lightning-sql.7.md | 22 ++++++++++++++++++++++ plugins/sql.c | 32 ++++++++++++++++++++++++++++++++ tests/test_plugin.py | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/doc/lightning-sql.7.md b/doc/lightning-sql.7.md index bb645244a..0b5c7d50a 100644 --- a/doc/lightning-sql.7.md +++ b/doc/lightning-sql.7.md @@ -53,6 +53,28 @@ represented as an integer in the database, so a query will return 0 or * JSON: string * sqlite3: TEXT +PERMITTED SQLITE3 FUNCTIONS +--------------------------- +Writing to the database is not permitted, and limits are placed +on various other query parameters. + +Additionally, only the following functions are allowed: + +* abs +* avg +* coalesce +* count +* hex +* quote +* length +* like +* lower +* upper +* min +* max +* sum +* total + TABLES ------ [comment]: # (GENERATE-DOC-START) diff --git a/plugins/sql.c b/plugins/sql.c index 46e854ceb..9890bac61 100644 --- a/plugins/sql.c +++ b/plugins/sql.c @@ -264,6 +264,38 @@ static int sqlite_authorize(void *dbq_, int code, return SQLITE_OK; } + /* Some functions are fairly necessary: */ + if (code == SQLITE_FUNCTION) { + if (streq(b, "abs")) + return SQLITE_OK; + if (streq(b, "avg")) + return SQLITE_OK; + if (streq(b, "coalesce")) + return SQLITE_OK; + if (streq(b, "count")) + return SQLITE_OK; + if (streq(b, "hex")) + return SQLITE_OK; + if (streq(b, "quote")) + return SQLITE_OK; + if (streq(b, "length")) + return SQLITE_OK; + if (streq(b, "like")) + return SQLITE_OK; + if (streq(b, "lower")) + return SQLITE_OK; + if (streq(b, "upper")) + return SQLITE_OK; + if (streq(b, "min")) + return SQLITE_OK; + if (streq(b, "max")) + return SQLITE_OK; + if (streq(b, "sum")) + return SQLITE_OK; + if (streq(b, "total")) + return SQLITE_OK; + } + /* See https://www.sqlite.org/c3ref/c_alter_table.html to decode these! */ dbq->authfail = tal_fmt(dbq, "Unauthorized: %u arg1=%s arg2=%s dbname=%s caller=%s", code, diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 237771fbc..b163f560f 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -3280,10 +3280,16 @@ def test_block_added_notifications(node_factory, bitcoind): @pytest.mark.openchannel('v2') @pytest.mark.developer("wants dev-announce-localhost so we see listnodes.addresses") def test_sql(node_factory, bitcoind): + opts = {'experimental-offers': None, + 'dev-allow-localhost': None} + l2opts = {'lease-fee-basis': 50, + 'lease-fee-base-sat': '2000msat', + 'channel-fee-max-base-msat': '500sat', + 'channel-fee-max-proportional-thousandths': 200, + 'sqlfilename': 'sql.sqlite3'} + l2opts.update(opts) l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, - opts={'experimental-offers': None, - 'sqlfilename': 'sql.sqlite3', - 'dev-allow-localhost': None}) + opts=[opts, l2opts, opts]) ret = l2.rpc.sql("SELECT * FROM forwards;") assert ret == {'rows': []} @@ -3794,6 +3800,27 @@ def test_sql(node_factory, bitcoind): with pytest.raises(RpcError, match='query failed with no such table: peers_channels'): l2.rpc.sql("SELECT * FROM peers_channels;") + # Test subobject case (option_will_fund) + ret = l2.rpc.sql("SELECT option_will_fund_lease_fee_base_msat," + " option_will_fund_lease_fee_basis," + " option_will_fund_funding_weight," + " option_will_fund_channel_fee_max_base_msat," + " option_will_fund_channel_fee_max_proportional_thousandths," + " option_will_fund_compact_lease" + " FROM nodes WHERE HEX(nodeid) = '{}';".format(l2.info['id'].upper())) + optret = only_one(l2.rpc.listnodes(l2.info['id'])['nodes'])['option_will_fund'] + row = only_one(ret['rows']) + assert row == [v for v in optret.values()] + + # Correctly handles missing object. + assert l2.rpc.sql("SELECT option_will_fund_lease_fee_base_msat," + " option_will_fund_lease_fee_basis," + " option_will_fund_funding_weight," + " option_will_fund_channel_fee_max_base_msat," + " option_will_fund_channel_fee_max_proportional_thousandths," + " option_will_fund_compact_lease" + " FROM nodes WHERE HEX(nodeid) = '{}';".format(l1.info['id'].upper())) == {'rows': [[None] * 6]} + def test_sql_deprecated(node_factory, bitcoind): # deprecated-apis breaks schemas...