Skip to content

Commit a79b05a

Browse files
committed
Add kv_list and refactor session_settings with it
A new structure, kv_list, represents a list of key/value pairs. It does not use an array of structs for each pair, because a custom GUC value must be allocated as a single block, The `kv_list` constructor takes a Postgres `List` argument, iterates over the keys and values, and then allocates a block of memory for them (with options to use `palloc`, `malloc`, or `guc_malloc` (which falls back on `malloc` prior to Postgrs 16)). It then iterates over them again to pack them into the memory space beyond the `kv_list` struct itself. The design follows the precedent of the `ConvertTimeZoneAbbrevs()` function in Postgres's `datetime.c`. Use it in `chfdw_check_settings_guc()` to create a `kv_list` from the `List` returned by `chfdw_parse_options()` and assign it to `**extra`. Postgres hands this pointer to `chfdw_settings_assign_hook()`, where we assign it to the `ch_session_settings_list` global, which can then be fetched via `chfdw_get_session_settings()`. Of course this would be a tricky interface to iterate manually, so also add another struct, `kv_iter`, that handles the logic of iterating through the keys and values in a `kv_list`. Since it sets up the values for each iteration of the loop, the logic using it is simpler than the previous use of `List`, as seen in the http and binary engines. While at it, cChange `chfdw_parse_options()` to use `pstrdup()` instead of `strdup`, since its memory is handled by a Postgres memory context (and surely was leaking before).
1 parent 91a6a04 commit a79b05a

File tree

7 files changed

+242
-40
lines changed

7 files changed

+242
-40
lines changed

src/binary.cpp

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,23 +173,15 @@ static void set_resp_error(ch_binary_response_t * resp, const char * str)
173173
*/
174174
static QuerySettings ch_binary_settings(const ch_query *query)
175175
{
176-
ListCell *lc;
177-
auto res = QuerySettings{};
178-
foreach (lc, (List *) query->settings)
179-
{
180-
/*
181-
* foreach reads a non-const, so we have to cast. Would be nice to use
182-
* foreach_ptr:
183-
*
184-
* foreach_ptr(DefElem, setting, query->settings)
185-
*
186-
* But it's only available in Postgres 17 and later.
187-
*/
188-
DefElem *setting = (DefElem *) lfirst(lc);
189-
res.insert_or_assign(setting->defname, QuerySettingsField{strVal(setting->arg), 1});
190-
}
176+
kv_iter iter;
177+
auto res = QuerySettings{};
191178

192-
return res;
179+
for (iter = new_kv_iter(query->settings); !kv_iter_done(&iter); kv_iter_next(&iter))
180+
{
181+
res.insert_or_assign(iter.name, QuerySettingsField{iter.value, 1});
182+
}
183+
184+
return res;
193185
}
194186

195187
static void set_state_error(ch_binary_read_state_t * state, const char * str)

src/http.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,7 @@ ch_http_simple_query(ch_http_connection_t * conn, const ch_query * query)
158158
static char errbuffer[CURL_ERROR_SIZE];
159159
struct curl_slist *headers = NULL;
160160
CURLU *cu = curl_url();
161-
ListCell *lc;
162-
DefElem *setting;
161+
kv_iter iter;
163162
char *buf = NULL;
164163

165164
ch_http_response_t *resp = calloc(sizeof(ch_http_response_t), 1);
@@ -178,10 +177,9 @@ ch_http_simple_query(ch_http_connection_t * conn, const ch_query * query)
178177
pfree(buf);
179178

180179
/* Append each of the settings as a query param. */
181-
foreach(lc, (List *) query->settings)
180+
for (iter = new_kv_iter(query->settings); !kv_iter_done(&iter); kv_iter_next(&iter))
182181
{
183-
setting = (DefElem *) lfirst(lc);
184-
buf = psprintf("%s=%s", setting->defname, strVal(setting->arg));
182+
buf = psprintf("%s=%s", iter.name, iter.value);
185183
curl_url_set(cu, CURLUPART_QUERY, buf, CURLU_APPENDQUERY | CURLU_URLENCODE);
186184
pfree(buf);
187185
}

src/include/engine.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#ifndef CLICKHOUSE_ENGINE_H
22
#define CLICKHOUSE_ENGINE_H
33

4-
#include "nodes/pathnodes.h"
4+
#include "kv_list.h"
55

66
/*
77
* ch_connection_details defines the details for connecting to ClickHouse.
@@ -20,10 +20,10 @@ typedef struct
2020
*/
2121
typedef struct
2222
{
23-
const char *sql;
24-
const List *settings;
25-
} ch_query;
23+
const char *sql;
24+
const kv_list *settings;
25+
} ch_query;
2626

27-
#define new_query(sql) {sql, chfdw_parse_options(ch_session_settings, true, false)}
27+
#define new_query(sql) {sql, chfdw_get_session_settings()}
2828

2929
#endif /* CLICKHOUSE_ENGINE_H */

src/include/fdw.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ extern void chfdw_report_error(int elevel, ch_connection conn,
199199
bool clear, const char *sql);
200200

201201
/* in option.c */
202-
extern char *ch_session_settings;
202+
extern kv_list * chfdw_get_session_settings(void);
203+
203204
extern void
204205
chfdw_extract_options(List * defelems, char **driver, char **host, int *port,
205206
char **dbname, char **username, char **password);

src/include/kv_list.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#ifndef PG_CLICKHOUSE_KV_LIST_H
2+
#define PG_CLICKHOUSE_KV_LIST_H
3+
4+
#include <stdbool.h>
5+
#include "postgres.h"
6+
#include "nodes/pathnodes.h"
7+
8+
/*
9+
* A simple data structure with a list of key/value string pairs. Use
10+
* new_kv_list_from_list() to create.
11+
*/
12+
typedef struct kv_list
13+
{
14+
int length;
15+
/* key/value char * pairs follow the length field. */
16+
} kv_list;
17+
18+
/*
19+
* Iterator for a kv_list. Use new_kv_iter() to create.
20+
*
21+
* for (kv_iter iter = new_kv_iter(kv); !kv_iter_done(&iter); kv_iter_next(&iter))
22+
* {
23+
* printf("%i, %s => %s\n", iter.num, iter.name, iter.value);
24+
* }
25+
*/
26+
typedef struct kv_iter {
27+
int togo;
28+
char * name;
29+
char * value;
30+
} kv_iter;
31+
32+
/*
33+
* Defines the allocator to use when creating a new kv_list.
34+
* kv_pair_guc_malloc is the same as kv_pair_malloc on Postgres 15 and
35+
* earlier, so be sure to free() the memory on those versions.
36+
*/
37+
enum kv_pair_alloc
38+
{
39+
kv_pair_guc_malloc,
40+
kv_pair_malloc,
41+
kv_pair_palloc,
42+
};
43+
44+
/*
45+
* Create a new kv_list from a PostgreSQL List of DefElem. Allocate the memory
46+
* using the specified allocator.
47+
*/
48+
kv_list * new_kv_list_from_pg_list(List * list, int allocate);
49+
50+
/* Create a new kv_iter for a key_pairs. */
51+
kv_iter new_kv_iter(const kv_list * ns);
52+
53+
/* Iterate to the next item. Returns false if there are no items. */
54+
bool kv_iter_next(kv_iter * state);
55+
56+
/* Retruns true if iteration by kv_iter is complete. */
57+
bool kv_iter_done(kv_iter * state);
58+
59+
#endif /* PG_CLICKHOUSE_KV_LIST_H */

src/kv_list.c

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#include <stddef.h>
2+
#include "kv_list.h"
3+
#include "utils/elog.h"
4+
#include "nodes/pathnodes.h"
5+
#include "utils/guc.h"
6+
7+
static const size_t kv_list_struct_size = MAXALIGN(sizeof(kv_list));
8+
9+
static kv_list * allocate_list(size_t size, int allocate)
10+
{
11+
kv_list *pairs;
12+
13+
switch (allocate)
14+
{
15+
case kv_pair_guc_malloc:
16+
/* Fall through to malloc prior to Postgres 16. */
17+
#if PG_VERSION_NUM >= 160000
18+
pairs = guc_malloc(ERROR, size);
19+
break;
20+
#endif
21+
case kv_pair_malloc:
22+
pairs = malloc(size);
23+
break;
24+
case kv_pair_palloc:
25+
pairs = palloc(size);
26+
break;
27+
default:
28+
ereport(ERROR,
29+
(errcode(ERRCODE_FDW_ERROR),
30+
errmsg("unknown kv_pair_alloc %i", allocate)));
31+
}
32+
33+
if (!pairs)
34+
ereport(ERROR,
35+
(errcode(ERRCODE_FDW_OUT_OF_MEMORY),
36+
errmsg("out of memory")));
37+
38+
return pairs;
39+
}
40+
41+
extern kv_list * new_kv_list_from_pg_list(List * list, int allocate)
42+
{
43+
ListCell *lc;
44+
DefElem *elem;
45+
kv_list *pairs;
46+
size_t ck_size = kv_list_struct_size;
47+
size_t kv_size = kv_list_struct_size;
48+
49+
/* Count up space for dynamic key/value pairs. */
50+
foreach(lc, list)
51+
{
52+
elem = (DefElem *) lfirst(lc);
53+
kv_size += 2 + strlen(elem->defname) + strlen(strVal(elem->arg));
54+
}
55+
56+
/* Alloc the result ... */
57+
pairs = allocate_list(kv_size, allocate);
58+
59+
/* ... and fill it in */
60+
pairs->length = list_length(list);
61+
62+
/* in this loop, size reprises the space calculation above */
63+
foreach(lc, list)
64+
{
65+
char *str;
66+
67+
/* Append the element name and arg to the kv_pair. */
68+
elem = (DefElem *) lfirst(lc);
69+
str = (char *) pairs + ck_size;
70+
strcpy(str, elem->defname);
71+
ck_size += strlen(str) + 1;
72+
str = (char *) pairs + ck_size;
73+
strcpy(str, strVal(elem->arg));
74+
ck_size += strlen(str) + 1;
75+
}
76+
77+
/* Assert the two loops above agreed on size calculations */
78+
Assert(kv_size == ck_size);
79+
80+
return pairs;
81+
}
82+
83+
extern kv_iter new_kv_iter(const kv_list * ns)
84+
{
85+
const size_t kv_size = kv_list_struct_size;
86+
char *name;
87+
88+
/* The list may be NULL or empty. */
89+
if (!ns || ns->length < 1)
90+
return (kv_iter)
91+
{
92+
0, "", ""
93+
};
94+
95+
/* Grab the number of pairs and char pointers to the name and value. */
96+
name = (char *) ns + kv_size;
97+
return (kv_iter)
98+
{
99+
ns->length, name, name + strlen(name) + 1
100+
};
101+
}
102+
103+
extern bool
104+
kv_iter_next(kv_iter * iter)
105+
{
106+
if (iter->togo == 0)
107+
return false;
108+
iter->togo--;
109+
iter->name += 2 + strlen(iter->name) + strlen(iter->value);
110+
iter->value = iter->name + strlen(iter->name) + 1;
111+
return true;
112+
}
113+
114+
extern bool
115+
kv_iter_done(kv_iter * iter)
116+
{
117+
return iter->togo == 0;
118+
}

src/option.c

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "postgres.h"
1616

1717
#include "fdw.h"
18+
#include "kv_list.h"
1819

1920
#include "access/reloptions.h"
2021
#include "catalog/pg_foreign_server.h"
@@ -25,6 +26,7 @@
2526
#include "nodes/makefuncs.h"
2627
#include "utils/guc.h"
2728
#include "utils/varlena.h"
29+
#include "utils/builtins.h"
2830

2931
static char *DEFAULT_DBNAME = "default";
3032

@@ -66,7 +68,8 @@ static const ChFdwOption ch_options[] =
6668
/*
6769
* GUC parameters
6870
*/
69-
char *ch_session_settings = NULL;
71+
static char *ch_session_settings = NULL;
72+
static kv_list * ch_session_settings_list = NULL;
7073

7174
/*
7275
* Helper functions
@@ -469,35 +472,66 @@ chfdw_parse_options(const char *options_string, bool with_comma, bool with_equal
469472
/*
470473
* Now that we have the name and the value, store the record.
471474
*/
472-
options = lappend(options, makeDefElem(strdup(pname), (Node *) makeString(strdup(pval)), -1));
475+
options = lappend(options, makeDefElem(pstrdup(pname), (Node *) makeString(pstrdup(pval)), -1));
473476
}
474477

475478
return options;
476479
}
477480

478481
/*
479-
* check_settings_guc
480-
*
482+
* Return the current list of current session settings parsed from the
483+
* session_settings GUC.
484+
*/
485+
kv_list *
486+
chfdw_get_session_settings()
487+
{
488+
return ch_session_settings_list;
489+
}
490+
491+
/*
481492
* Validates the provided settings key/value pairs.
482493
*/
483494
static bool
484-
check_settings_guc(char **newval, void **extra, GucSource source)
495+
chfdw_check_settings_guc(char **newval, void **extra, GucSource source)
485496
{
486497
/*
487498
* The value may be an empty string, so we have to accept that value.
499+
* Leave extra unset; chfdw_settings_assign_hook() will assign NULL to
500+
* ch_session_settings_list.
488501
*/
489502
if (*newval == NULL || *newval[0] == '\0')
490503
return true;
491504

492-
/*
493-
* Make sure we can parse the settings.
494-
*/
495-
chfdw_parse_options(*newval, true, false);
505+
/* Make sure we can parse the settings. */
506+
List *list = chfdw_parse_options(*newval, true, false);
507+
508+
if (!list)
509+
return false;
510+
511+
/* Convert them into a guc_malloc'd kv_list. */
512+
kv_list *settings = new_kv_list_from_pg_list(list, kv_pair_guc_malloc);
496513

514+
if (!settings)
515+
return false;
516+
517+
/* All good; stash for chfdw_settings_assign_hook and return true. */
518+
*extra = settings;
519+
return true;
520+
}
521+
522+
/*
523+
* Assigns the kv_list stored in extra to the ch_session_settings_list global.
524+
*/
525+
static void
526+
chfdw_settings_assign_hook(const char *newval, void *extra)
527+
{
497528
/*
498-
* All good if no error.
529+
* From PostgreSQL's POV: (a) failure here is not acceptable, and (b) it
530+
* is not necessarily called inside a transaction, so e.g. catalog lookups
531+
* are not okay. IOW, keep it as simple as possible, and leave error
532+
* returning behavior to chfdw_check_settings_guc().
499533
*/
500-
return true;
534+
ch_session_settings_list = (kv_list *) extra;
501535
}
502536

503537
/*
@@ -522,8 +556,8 @@ _PG_init(void)
522556
"join_use_nulls 1, group_by_use_nulls 1, final 1",
523557
PGC_USERSET,
524558
0,
525-
check_settings_guc,
526-
NULL,
559+
chfdw_check_settings_guc,
560+
chfdw_settings_assign_hook,
527561
NULL);
528562

529563
#if PG_VERSION_NUM >= 150000

0 commit comments

Comments
 (0)