Skip to content

Commit 28cf990

Browse files
committed
plugins/sql: listsqlschemas command to retrieve schemas.
Good for detection of what fields are present. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent 8d60994 commit 28cf990

File tree

8 files changed

+331
-0
lines changed

8 files changed

+331
-0
lines changed

doc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ MANPAGES := doc/lightning-cli.1 \
6060
doc/lightning-listpeers.7 \
6161
doc/lightning-listpeerchannels.7 \
6262
doc/lightning-listsendpays.7 \
63+
doc/lightning-listsqlschemas.7 \
6364
doc/lightning-makesecret.7 \
6465
doc/lightning-multifundchannel.7 \
6566
doc/lightning-multiwithdraw.7 \

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Core Lightning Documentation
8888
lightning-listpeerchannels <lightning-listpeerchannels.7.md>
8989
lightning-listpeers <lightning-listpeers.7.md>
9090
lightning-listsendpays <lightning-listsendpays.7.md>
91+
lightning-listsqlschemas <lightning-listsqlschemas.7.md>
9192
lightning-listtransactions <lightning-listtransactions.7.md>
9293
lightning-makesecret <lightning-makesecret.7.md>
9394
lightning-multifundchannel <lightning-multifundchannel.7.md>

doc/lightning-listsqlschemas.7.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
lightning-listsqlschemas -- Command to example lightning-sql schemas
2+
====================================================================
3+
4+
SYNOPSIS
5+
--------
6+
7+
**listsqlschemas** [*table*]
8+
9+
DESCRIPTION
10+
-----------
11+
12+
This allows you to examine the schemas at runtime; while they are fully
13+
documented for the current release in lightning-sql(7), as fields are
14+
added or deprecated, you can use this command to determine what fields
15+
are present.
16+
17+
If *table* is given, only that table is in the resulting list, otherwise
18+
all tables are listed.
19+
20+
EXAMPLE JSON REQUEST
21+
------------
22+
```json
23+
{
24+
"id": 82,
25+
"method": "listsqlschemas",
26+
"params": {
27+
"table": "offers"
28+
}
29+
}
30+
```
31+
32+
EXAMPLE JSON RESPONSE
33+
-----
34+
```json
35+
{
36+
"schemas": [
37+
{
38+
"tablename": "offers",
39+
"columns": [
40+
{
41+
"name": "offer_id",
42+
"type": "BLOB"
43+
},
44+
{
45+
"name": "active",
46+
"type": "INTEGER"
47+
},
48+
{
49+
"name": "single_use",
50+
"type": "INTEGER"
51+
},
52+
{
53+
"name": "bolt12",
54+
"type": "TEXT"
55+
},
56+
{
57+
"name": "bolt12_unsigned",
58+
"type": "TEXT"
59+
},
60+
{
61+
"name": "used",
62+
"type": "INTEGER"
63+
},
64+
{
65+
"name": "label",
66+
"type": "TEXT"
67+
}
68+
],
69+
"indices": [
70+
[
71+
"offer_id"
72+
]
73+
]
74+
}
75+
]
76+
}
77+
```
78+
79+
RETURN VALUE
80+
------------
81+
82+
[comment]: # (GENERATE-FROM-SCHEMA-START)
83+
On success, an object containing **schemas** is returned. It is an array of objects, where each object contains:
84+
85+
- **tablename** (string): the name of the table
86+
- **columns** (array of objects): the columns, in database order:
87+
- **name** (string): the name the column
88+
- **type** (string): the SQL type of the column (one of "INTEGER", "BLOB", "TEXT", "REAL")
89+
- **indices** (array of arrays, optional): Any index we created to speed lookups:
90+
- The columns for this index:
91+
- The column name
92+
93+
[comment]: # (GENERATE-FROM-SCHEMA-END)
94+
95+
AUTHOR
96+
------
97+
98+
Rusty Russell <<rusty@rustcorp.com.au>> is mainly responsible.
99+
100+
SEE ALSO
101+
--------
102+
103+
lightning-sql(7).
104+
105+
RESOURCES
106+
---------
107+
108+
Main web site: <https://github.com/ElementsProject/lightning>
109+
[comment]: # ( SHA256STAMP:3ac985dd8ef6959b327e6e6a79079db3ad51423bc4e469799a12ae74b2e75697)

doc/lightning-sql.7.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ cache `listnodes` and `listchannels`) which then processes the results.
1919
It is, however faster for remote access if the result of the query is
2020
much smaller than the list commands would be.
2121

22+
Note that queries like "SELECT *" are fragile, as columns will
23+
change across releases; see lightning-listsqlschemas(7).
24+
2225
TREATMENT OF TYPES
2326
------------------
2427

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"required": [],
5+
"properties": {
6+
"table": {
7+
"type": "string"
8+
}
9+
}
10+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"additionalProperties": false,
5+
"required": [
6+
"schemas"
7+
],
8+
"properties": {
9+
"schemas": {
10+
"type": "array",
11+
"items": {
12+
"type": "object",
13+
"additionalProperties": false,
14+
"required": [
15+
"tablename",
16+
"columns"
17+
],
18+
"properties": {
19+
"tablename": {
20+
"type": "string",
21+
"description": "the name of the table"
22+
},
23+
"columns": {
24+
"type": "array",
25+
"description": "the columns, in database order",
26+
"items": {
27+
"type": "object",
28+
"additionalProperties": false,
29+
"required": [
30+
"name",
31+
"type"
32+
],
33+
"properties": {
34+
"name": {
35+
"type": "string",
36+
"description": "the name the column"
37+
},
38+
"type": {
39+
"type": "string",
40+
"enum": [
41+
"INTEGER",
42+
"BLOB",
43+
"TEXT",
44+
"REAL"
45+
],
46+
"description": "the SQL type of the column"
47+
}
48+
}
49+
}
50+
},
51+
"indices": {
52+
"type": "array",
53+
"description": "Any index we created to speed lookups",
54+
"items": {
55+
"type": "array",
56+
"description": "The columns for this index",
57+
"items": {
58+
"type": "string",
59+
"description": "The column name"
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}

plugins/sql.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,111 @@ static bool ignore_column(const struct table_desc *td, const jsmntok_t *t)
10011001
return false;
10021002
}
10031003

1004+
static struct command_result *param_tablename(struct command *cmd,
1005+
const char *name,
1006+
const char *buffer,
1007+
const jsmntok_t *tok,
1008+
struct table_desc **td)
1009+
{
1010+
*td = strmap_getn(&tablemap, buffer + tok->start,
1011+
tok->end - tok->start);
1012+
if (!*td)
1013+
return command_fail_badparam(cmd, name, buffer, tok,
1014+
"Unknown table");
1015+
return NULL;
1016+
}
1017+
1018+
static void json_add_column(struct json_stream *js,
1019+
const char *dbname,
1020+
const char *sqltypename)
1021+
{
1022+
json_object_start(js, NULL);
1023+
json_add_string(js, "name", dbname);
1024+
json_add_string(js, "type", sqltypename);
1025+
json_object_end(js);
1026+
}
1027+
1028+
static void json_add_columns(struct json_stream *js,
1029+
const struct table_desc *td)
1030+
{
1031+
for (size_t i = 0; i < tal_count(td->columns); i++) {
1032+
if (td->columns[i].sub) {
1033+
if (td->columns[i].sub->is_subobject)
1034+
json_add_columns(js, td->columns[i].sub);
1035+
continue;
1036+
}
1037+
json_add_column(js, td->columns[i].dbname,
1038+
fieldtypemap[td->columns[i].ftype].sqltype);
1039+
}
1040+
}
1041+
1042+
static void json_add_schema(struct json_stream *js,
1043+
const struct table_desc *td)
1044+
{
1045+
bool have_indices;
1046+
1047+
json_object_start(js, NULL);
1048+
json_add_string(js, "tablename", td->name);
1049+
/* This needs to be an array, not a dictionary, since dicts
1050+
* are often treated as unordered, and order is critical! */
1051+
json_array_start(js, "columns");
1052+
if (td->parent) {
1053+
json_add_column(js, "row", "INTEGER");
1054+
json_add_column(js, "arrindex", "INTEGER");
1055+
}
1056+
json_add_columns(js, td);
1057+
json_array_end(js);
1058+
1059+
/* Don't print indices entry unless we have an index! */
1060+
have_indices = false;
1061+
for (size_t i = 0; i < ARRAY_SIZE(indices); i++) {
1062+
if (!streq(indices[i].tablename, td->name))
1063+
continue;
1064+
if (!have_indices) {
1065+
json_array_start(js, "indices");
1066+
have_indices = true;
1067+
}
1068+
json_array_start(js, NULL);
1069+
for (size_t j = 0; j < ARRAY_SIZE(indices[i].fields); j++) {
1070+
if (indices[i].fields[j])
1071+
json_add_string(js, NULL, indices[i].fields[j]);
1072+
}
1073+
json_array_end(js);
1074+
}
1075+
if (have_indices)
1076+
json_array_end(js);
1077+
json_object_end(js);
1078+
}
1079+
1080+
static bool add_one_schema(const char *member, struct table_desc *td,
1081+
struct json_stream *js)
1082+
{
1083+
json_add_schema(js, td);
1084+
return true;
1085+
}
1086+
1087+
static struct command_result *json_listsqlschemas(struct command *cmd,
1088+
const char *buffer,
1089+
const jsmntok_t *params)
1090+
{
1091+
struct table_desc *td;
1092+
struct json_stream *ret;
1093+
1094+
if (!param(cmd, buffer, params,
1095+
p_opt("table", param_tablename, &td),
1096+
NULL))
1097+
return command_param_failed();
1098+
1099+
ret = jsonrpc_stream_success(cmd);
1100+
json_array_start(ret, "schemas");
1101+
if (td)
1102+
json_add_schema(ret, td);
1103+
else
1104+
strmap_iterate(&tablemap, add_one_schema, ret);
1105+
json_array_end(ret);
1106+
return command_finished(cmd, ret);
1107+
}
1108+
10041109
/* Creates sql statements, initializes table */
10051110
static void finish_td(struct plugin *plugin, struct table_desc *td)
10061111
{
@@ -1353,6 +1458,13 @@ static const struct plugin_command commands[] = { {
13531458
"This is the greatest plugin command ever!",
13541459
json_sql,
13551460
},
1461+
{
1462+
"listsqlschemas",
1463+
"misc",
1464+
"Display schemas for internal sql tables, or just {table}",
1465+
"This is the greatest plugin command ever!",
1466+
json_listsqlschemas,
1467+
},
13561468
};
13571469

13581470
static const char *fmt_indexes(const tal_t *ctx, const char *table)

tests/test_plugin.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3783,6 +3783,34 @@ def test_sql(node_factory, bitcoind):
37833783
{'name': 'payment_id',
37843784
'type': 'hex'}]}}
37853785

3786+
sqltypemap = {'string': 'TEXT',
3787+
'boolean': 'INTEGER',
3788+
'u8': 'INTEGER',
3789+
'u16': 'INTEGER',
3790+
'u32': 'INTEGER',
3791+
'u64': 'INTEGER',
3792+
'msat': 'INTEGER',
3793+
'hex': 'BLOB',
3794+
'hash': 'BLOB',
3795+
'txid': 'BLOB',
3796+
'pubkey': 'BLOB',
3797+
'secret': 'BLOB',
3798+
'number': 'REAL',
3799+
'short_channel_id': 'TEXT'}
3800+
3801+
# Check schemas match.
3802+
for table, schema in expected_schemas.items():
3803+
res = only_one(l2.rpc.listsqlschemas(table)['schemas'])
3804+
assert res['tablename'] == table
3805+
assert res.get('indices') == schema.get('indices')
3806+
sqlcolumns = [{'name': c['name'], 'type': sqltypemap[c['type']]} for c in schema['columns']]
3807+
assert res['columns'] == sqlcolumns
3808+
3809+
# Make sure we didn't miss any
3810+
assert (sorted([s['tablename'] for s in l1.rpc.listsqlschemas()['schemas']])
3811+
== sorted(expected_schemas.keys()))
3812+
assert len(l1.rpc.listsqlschemas()['schemas']) == len(expected_schemas)
3813+
37863814
# Very rough checks of other list commands (make sure l2 has one of each)
37873815
l2.rpc.offer(1, 'desc')
37883816
l2.rpc.invoice(1, 'label', 'desc')

0 commit comments

Comments
 (0)