Skip to content

Commit 017ec79

Browse files
committed
Add kv_list and refactor session_settings with it
A new structure, kv_list, represents a list of key/value pairs. Its constructor takes a Postgres `List` argument, iterates over the keys and values, allocates memory for each, and copies them into the struct. It uses `malloc()` to allocate the memory, because the we store the value in a global that mustn't be subject to the GUC memory context. Use it in `chfdw_settings_assign_hook()` to create a `kv_list` from the `List` returned by `chfdw_parse_options()` and assign it to the `ch_session_settings_list` global, which can then be fetched via `chfdw_get_session_settings()`. The downside to this approach is that the new value must be parsed twice. We consider this acceptable, since it means they're no longer parsed for every query. While at it, change `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 797965f commit 017ec79

File tree

7 files changed

+223
-39
lines changed

7 files changed

+223
-39
lines changed

src/binary.cpp

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,23 +173,17 @@ 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+
int i;
177+
kv_pair *pair;
178+
auto res = QuerySettings{};
191179

192-
return res;
180+
for (i = 0; i < query->settings->length; i++)
181+
{
182+
pair = query->settings->items[i];
183+
res.insert_or_assign(pair->name, QuerySettingsField{pair->value, 1});
184+
}
185+
186+
return res;
193187
}
194188

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

src/http.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ 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+
int i;
162+
kv_pair *pair;
163163
char *buf = NULL;
164164

165165
ch_http_response_t *resp = calloc(sizeof(ch_http_response_t), 1);
@@ -178,10 +178,11 @@ ch_http_simple_query(ch_http_connection_t * conn, const ch_query * query)
178178
pfree(buf);
179179

180180
/* Append each of the settings as a query param. */
181-
foreach(lc, (List *) query->settings)
181+
182+
for (i = 0; i < query->settings->length; i++)
182183
{
183-
setting = (DefElem *) lfirst(lc);
184-
buf = psprintf("%s=%s", setting->defname, strVal(setting->arg));
184+
pair = query->settings->items[i];
185+
buf = psprintf("%s=%s", pair->name, pair->value);
185186
curl_url_set(cu, CURLUPART_QUERY, buf, CURLU_APPENDQUERY | CURLU_URLENCODE);
186187
pfree(buf);
187188
}

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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
/* A key/value pair. */
9+
typedef struct kv_pair
10+
{
11+
char *name;
12+
char *value;
13+
} kv_pair;
14+
15+
/*
16+
* A simple data structure with a list of key/value string pairs. Use
17+
* new_kv_list_from_list() to create.
18+
*
19+
* kv_list * pairs = new_kv_list_from_pg_list(list);
20+
* if (!kv_list)
21+
* {
22+
* printf("out of memory\n");
23+
* exit(1);
24+
* }
25+
*
26+
* for (int i = 0; i < kv_list->length; i++) {
27+
* kv_pair * pair = kv_list->items[i];
28+
* printf("%s => %s\n", pair->name, pair->value);
29+
* }
30+
*
31+
* kv_list_free(pairs);
32+
*/
33+
typedef struct kv_list
34+
{
35+
int length;
36+
kv_pair **items;
37+
} kv_list;
38+
39+
/*
40+
* Create an empty kv_list, with length set to zero and items uninitialized.
41+
*/
42+
kv_list * new_empty_kv_list(void);
43+
44+
/*
45+
* Create a new kv_list from a PostgreSQL List of DefElem. Logs an error
46+
* message and returns NULL on memory errors.
47+
*/
48+
kv_list * new_kv_list_from_pg_list(List * list);
49+
50+
/* Frees the memory owned by the kv_list. */
51+
void kv_list_free(kv_list * pairs);
52+
53+
#endif /* PG_CLICKHOUSE_KV_LIST_H */

src/kv_list.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
extern kv_list * new_empty_kv_list(void)
8+
{
9+
kv_list *pairs = (kv_list *) malloc(sizeof(kv_list));
10+
11+
if (pairs == NULL)
12+
{
13+
ereport(LOG, errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory"));
14+
return NULL;
15+
}
16+
pairs->length = 0;
17+
return pairs;
18+
}
19+
20+
extern kv_list * new_kv_list_from_pg_list(List * list)
21+
{
22+
ListCell *lc;
23+
DefElem *elem;
24+
kv_list *pairs;
25+
int i = 0;
26+
27+
/* Allocate the memory for the pairs object. */
28+
pairs = (kv_list *) malloc(sizeof(kv_list));
29+
if (pairs == NULL)
30+
{
31+
ereport(LOG, errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory"));
32+
return NULL;
33+
}
34+
35+
/* Allocate the memory for kv_pair pointers to all the items in list. */
36+
pairs->items = (kv_pair * *) malloc(list_length(list) * sizeof(kv_pair *));
37+
if (pairs->items == NULL)
38+
{
39+
free(pairs);
40+
ereport(LOG, errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory"));
41+
return NULL;
42+
}
43+
pairs->length = list_length(list);
44+
45+
/* Copy the values from list into pairs. */
46+
foreach(lc, list)
47+
{
48+
kv_pair *pair = malloc(sizeof(kv_pair));
49+
50+
if (!pair)
51+
{
52+
kv_list_free(pairs);
53+
ereport(LOG, errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory"));
54+
return NULL;
55+
}
56+
elem = (DefElem *) lfirst(lc);
57+
pair->name = strdup(elem->defname);
58+
pair->value = strdup(strVal(elem->arg));
59+
if (!pair->name || !pair->value)
60+
{
61+
kv_list_free(pairs);
62+
ereport(LOG, errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("out of memory"));
63+
return NULL;
64+
}
65+
pairs->items[i] = pair;
66+
i++;
67+
}
68+
69+
return pairs;
70+
}
71+
72+
extern void
73+
kv_list_free(kv_list * pairs)
74+
{
75+
if (pairs == NULL)
76+
return;
77+
for (int i = 0; i < pairs->length; i++)
78+
if (pairs->items[i] != NULL)
79+
{
80+
if (pairs->items[i]->name)
81+
free(pairs->items[i]->name);
82+
if (pairs->items[i]->value)
83+
free(pairs->items[i]->value);
84+
free(pairs->items[i]);
85+
}
86+
free(pairs->items);
87+
free(pairs);
88+
}

src/option.c

Lines changed: 59 additions & 12 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,79 @@ 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-
*/
505+
/* Make sure we can parse the settings. */
495506
chfdw_parse_options(*newval, true, false);
496507

508+
/* All good if no errors. */
509+
return true;
510+
}
511+
512+
/*
513+
* Assigns the kv_list stored in extra to the ch_session_settings_list global.
514+
*/
515+
static void
516+
chfdw_settings_assign_hook(const char *newval, void *extra)
517+
{
497518
/*
498-
* All good if no error.
519+
* From PostgreSQL's POV: (a) failure here is not acceptable, and (b) it
520+
* is not necessarily called inside a transaction, so e.g. catalog lookups
521+
* are not okay. IOW, keep it as simple as possible, and leave error
522+
* returning behavior to chfdw_check_settings_guc(). Therefore wrap the
523+
* parsing in PG_TRY() to prevent errors.
499524
*/
500-
return true;
525+
kv_list_free(ch_session_settings_list);
526+
PG_TRY();
527+
{
528+
if (newval == NULL || newval[0] == '\0')
529+
ch_session_settings_list = new_empty_kv_list();
530+
else
531+
{
532+
List *list = chfdw_parse_options(newval, true, false);
533+
534+
ch_session_settings_list = new_kv_list_from_pg_list(list);
535+
}
536+
}
537+
PG_CATCH();
538+
{
539+
/*
540+
* This should not happen, since chfdw_check_settings_guc successfully
541+
* parsed the options, but we could get here due to memory errors.
542+
*/
543+
ereport(LOG,
544+
(errcode(ERRCODE_FDW_ERROR),
545+
errmsg("unexpected error parsing \"%s\"", newval)));
546+
}
547+
PG_END_TRY();
501548
}
502549

503550
/*
@@ -522,8 +569,8 @@ _PG_init(void)
522569
"join_use_nulls 1, group_by_use_nulls 1, final 1",
523570
PGC_USERSET,
524571
0,
525-
check_settings_guc,
526-
NULL,
572+
chfdw_check_settings_guc,
573+
chfdw_settings_assign_hook,
527574
NULL);
528575

529576
#if PG_VERSION_NUM >= 150000

0 commit comments

Comments
 (0)