diff --git a/devtools/sql-rewrite.py b/devtools/sql-rewrite.py index 4a93a9d9b..6cfb44ce6 100755 --- a/devtools/sql-rewrite.py +++ b/devtools/sql-rewrite.py @@ -83,13 +83,58 @@ rewriters = { "postgres": PostgresRewriter(), } + +# djb2 is simple and effective: see http://www.cse.yorku.ca/~oz/hash.html +def hash_djb2(string): + val = 5381 + for s in string: + val = ((val * 33) & 0xFFFFFFFF) ^ ord(s) + return val + + +def colname_htable(query): + assert query.upper().startswith("SELECT") + colquery = query[6:query.upper().index(" FROM ")] + colnames = colquery.split(',') + + # If split caused unbalanced brackets, it's complex: assume + # a single field! + if any([colname.count('(') != colname.count(')') for colname in colnames]): + return [('"' + colquery.strip() + '"', 0)] + + # 50% density htable + tablesize = len(colnames) * 2 - 1 + table = [("NULL", -1)] * tablesize + for colnum, colname in enumerate(colnames): + colname = colname.strip() + # SELECT xxx AS yyy -> Y + as_clause = colname.upper().find(" AS ") + if as_clause != -1: + colname = colname[as_clause + 4:].strip() + + pos = hash_djb2(colname) % tablesize + while table[pos][0] != "NULL": + pos = (pos + 1) % tablesize + table[pos] = ('"' + colname + '"', colnum) + return table + + template = Template("""#ifndef LIGHTNINGD_WALLET_GEN_DB_${f.upper()} #define LIGHTNINGD_WALLET_GEN_DB_${f.upper()} #include +#include #include #if HAVE_${f.upper()} +% for colname, table in colhtables.items(): +static const struct sqlname_map ${colname}[] = { +% for t in table: + { ${t[0]}, ${t[1]} }, +% endfor +}; + +% endfor struct db_query db_${f}_queries[] = { @@ -99,6 +144,10 @@ struct db_query db_${f}_queries[] = { .query = "${elem['query']}", .placeholders = ${elem['placeholders']}, .readonly = ${elem['readonly']}, +% if elem['colnames'] is not None: + .colnames = ${elem['colnames']}, + .num_colnames = ARRAY_SIZE(${elem['colnames']}), +% endif }, % endfor }; @@ -129,6 +178,7 @@ def extract_queries(pofile): if chunk != []: yield chunk + colhtables = {} queries = [] for c in chunk(pofile): @@ -140,13 +190,21 @@ def extract_queries(pofile): # Strip header and surrounding quotes query = c[i][7:][:-1] + is_select = query.upper().startswith("SELECT") + if is_select: + colnames = 'col_table{}'.format(len(queries)) + colhtables[colnames] = colname_htable(query) + else: + colnames = None + queries.append({ 'name': query, 'query': query, 'placeholders': query.count('?'), - 'readonly': "true" if query.upper().startswith("SELECT") else "false", + 'readonly': "true" if is_select else "false", + 'colnames': colnames, }) - return queries + return colhtables, queries if __name__ == "__main__": @@ -165,7 +223,7 @@ if __name__ == "__main__": rewriter = rewriters[dialect] - queries = extract_queries(sys.argv[1]) + colhtables, queries = extract_queries(sys.argv[1]) queries = rewriter.rewrite(queries) - print(template.render(f=dialect, queries=queries)) + print(template.render(f=dialect, queries=queries, colhtables=colhtables)) diff --git a/wallet/db_common.h b/wallet/db_common.h index 5db081a6f..5ca35ffac 100644 --- a/wallet/db_common.h +++ b/wallet/db_common.h @@ -49,6 +49,10 @@ struct db_query { /* Is this a read-only query? If it is there's no need to tell plugins * about it. */ bool readonly; + + /* If this is a select statement, what column names */ + const struct sqlname_map *colnames; + size_t num_colnames; }; enum db_binding_type { @@ -155,5 +159,10 @@ AUTODATA_TYPE(db_backends, struct db_config); */ void db_changes_add(struct db_stmt *db_stmt, const char * expanded); +/* devtools/sql-rewrite.py generates this simple htable */ +struct sqlname_map { + const char *sqlname; + int val; +}; #endif /* LIGHTNING_WALLET_DB_COMMON_H */ diff --git a/wallet/wallet.c b/wallet/wallet.c index 1492f358a..07444f139 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -4321,7 +4321,7 @@ struct amount_msat wallet_total_forward_fees(struct wallet *w) stmt = db_prepare_v2(w->db, SQL("SELECT" " CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)" - "FROM forwarded_payments " + " FROM forwarded_payments " "WHERE state = ?;")); db_bind_int(stmt, 0, wallet_forward_status_in_db(FORWARD_SETTLED)); db_query_prepared(stmt);