Skip to content

Commit 0e32e88

Browse files
committed
Refactor: pre-parse distribution_columns GUC with SplitIdentifierString
Replace the manual strtok_r-based parsing that ran on every table creation with a pre-parsed List maintained by GUC hooks: - Add CheckDistributionColumns (check hook): validates the comma- separated identifier list via SplitIdentifierString before the value is accepted. Invalid input (e.g. double commas) is rejected without touching the current parsed list. - Add AssignDistributionColumns (assign hook): parses the validated string into a List of char* pointers in TopMemoryContext so it survives transaction boundaries. A static 'previousRawList' keeps the backing string alive (SplitIdentifierString returns pointers into its input buffer). - Refactor FindMatchingDistributionColumn and FindMatchingDistributionColumnFromTargetList to iterate the pre-parsed list instead of re-tokenizing on every call. - Rename DistributionColumnsGUC → DistributionColumns and AssignDistributionColumnsGUC → AssignDistributionColumns. Behavioral change: the GUC now uses standard PostgreSQL identifier rules (unquoted names are downcased, quoted names preserve case). To match a case-sensitive column like "Tenant_Id", set the GUC to '"Tenant_Id"'. Tests updated accordingly. All check-post-citus14 tests pass.
1 parent f4722ab commit 0e32e88

5 files changed

Lines changed: 183 additions & 117 deletions

File tree

src/backend/distributed/commands/table.c

Lines changed: 131 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@
1010

1111
#include "postgres.h"
1212

13+
#include "miscadmin.h"
14+
1315
#include "access/genam.h"
1416
#include "access/htup_details.h"
1517
#include "access/xact.h"
1618
#include "catalog/index.h"
1719
#include "catalog/pg_attrdef.h"
1820
#include "catalog/pg_class.h"
21+
#include "catalog/pg_collation.h"
1922
#include "catalog/pg_constraint.h"
2023
#include "catalog/pg_depend.h"
2124
#include "catalog/pg_type.h"
25+
#include "commands/defrem.h"
2226
#include "commands/tablecmds.h"
27+
#include "executor/spi.h"
2328
#include "foreign/foreign.h"
2429
#include "lib/stringinfo.h"
25-
#include "miscadmin.h"
26-
#include "commands/defrem.h"
27-
#include "executor/spi.h"
2830
#include "nodes/nodeFuncs.h"
2931
#include "nodes/parsenodes.h"
3032
#include "parser/parse_expr.h"
@@ -35,6 +37,7 @@
3537
#include "utils/lsyscache.h"
3638
#include "utils/ruleutils.h"
3739
#include "utils/syscache.h"
40+
#include "utils/varlena.h"
3841

3942
#include "pg_version_constants.h"
4043

@@ -78,7 +81,80 @@ bool AllowUnsafeConstraints = false;
7881
* Citus walks the list in order and distributes by the first column that exists
7982
* in the table. Applies to CREATE TABLE and CREATE TABLE AS SELECT.
8083
*/
81-
char *DistributionColumnsGUC = "";
84+
char *DistributionColumns = "";
85+
86+
/* Pre-parsed list of distribution column names (List of String nodes).
87+
* Updated by AssignDistributionColumns whenever the GUC changes. */
88+
List *ParsedDistributionColumns = NIL;
89+
90+
91+
/*
92+
* CheckDistributionColumns is the GUC check hook for
93+
* citus.distribution_columns. It validates the comma-separated identifier
94+
* list using SplitIdentifierString and rejects invalid input (e.g. empty
95+
* tokens from double commas) before the assign hook runs.
96+
*/
97+
bool
98+
CheckDistributionColumns(char **newval, void **extra, GucSource source)
99+
{
100+
if (*newval == NULL || (*newval)[0] == '\0')
101+
{
102+
return true;
103+
}
104+
105+
char *rawCopy = pstrdup(*newval);
106+
List *parsed = NIL;
107+
bool valid = SplitIdentifierString(rawCopy, ',', &parsed);
108+
109+
list_free(parsed);
110+
pfree(rawCopy);
111+
112+
return valid;
113+
}
114+
115+
116+
/*
117+
* AssignDistributionColumns is the GUC assign hook for
118+
* citus.distribution_columns. It parses the comma-separated string into
119+
* a list of char* pointers (via SplitIdentifierString) so that callers
120+
* don't re-tokenize on every use. The check hook has already validated
121+
* the input, so SplitIdentifierString will always succeed here.
122+
*/
123+
void
124+
AssignDistributionColumns(const char *newval, void *extra)
125+
{
126+
/*
127+
* SplitIdentifierString modifies the input string in-place and returns
128+
* a list of char* pointers into that string. We must keep the backing
129+
* string alive as long as ParsedDistributionColumns references it, and
130+
* free it on the next call. Everything must live in TopMemoryContext
131+
* so it survives transaction boundaries.
132+
*/
133+
static char *previousRawList = NULL;
134+
135+
list_free(ParsedDistributionColumns);
136+
ParsedDistributionColumns = NIL;
137+
138+
if (previousRawList != NULL)
139+
{
140+
pfree(previousRawList);
141+
previousRawList = NULL;
142+
}
143+
144+
if (!newval || newval[0] == '\0')
145+
{
146+
return;
147+
}
148+
149+
MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
150+
151+
previousRawList = pstrdup(newval);
152+
153+
SplitIdentifierString(previousRawList, ',', &ParsedDistributionColumns);
154+
155+
MemoryContextSwitchTo(oldContext);
156+
}
157+
82158

83159
/* Local functions forward declarations for unsupported command checks */
84160
static void PostprocessCreateTableStmtForeignKeys(CreateStmt *createStatement);
@@ -4251,7 +4327,6 @@ ErrorIfTableHasIdentityColumn(Oid relationId)
42514327
}
42524328

42534329

4254-
42554330
/*
42564331
* FindMatchingDistributionColumnFromTargetList walks the GUC priority list
42574332
* and returns the first column name that appears in the given Query's
@@ -4262,78 +4337,68 @@ ErrorIfTableHasIdentityColumn(Oid relationId)
42624337
static char *
42634338
FindMatchingDistributionColumnFromTargetList(List *targetList, List *colNames)
42644339
{
4265-
if (DistributionColumnsGUC == NULL || DistributionColumnsGUC[0] == '\0')
4340+
if (ParsedDistributionColumns == NIL)
42664341
{
42674342
return NULL;
42684343
}
42694344

4270-
/* work on a copy so we don't modify the GUC value */
4271-
char *rawList = pstrdup(DistributionColumnsGUC);
4272-
char *token = NULL;
4273-
char *savePtr = NULL;
4345+
/* Build list of available column names from colNames or targetList */
4346+
List *availableCols = NIL;
4347+
bool freeAvailableCols = false;
42744348

4275-
for (token = strtok_r(rawList, ",", &savePtr);
4276-
token != NULL;
4277-
token = strtok_r(NULL, ",", &savePtr))
4349+
if (colNames != NIL)
42784350
{
4279-
/* trim leading whitespace */
4280-
while (*token == ' ' || *token == '\t')
4281-
{
4282-
token++;
4283-
}
4284-
4285-
/* trim trailing whitespace */
4286-
char *end = token + strlen(token) - 1;
4287-
while (end > token && (*end == ' ' || *end == '\t'))
4351+
/* colNames is a List of String nodes; extract raw char* */
4352+
ListCell *cnCell = NULL;
4353+
foreach(cnCell, colNames)
42884354
{
4289-
*end = '\0';
4290-
end--;
4355+
availableCols = lappend(availableCols, strVal(lfirst(cnCell)));
42914356
}
4292-
4293-
/* skip empty tokens */
4294-
if (*token == '\0')
4295-
{
4296-
continue;
4297-
}
4298-
4299-
/*
4300-
* Walk the target list to check if any output column matches.
4301-
* If colNames is provided, use those names instead.
4302-
*/
4303-
ListCell *colCell = list_head(colNames);
4304-
TargetEntry *tle = NULL;
4305-
foreach_declared_ptr(tle, targetList)
4357+
freeAvailableCols = true;
4358+
}
4359+
else
4360+
{
4361+
ListCell *tlCell = NULL;
4362+
foreach(tlCell, targetList)
43064363
{
4307-
if (tle->resjunk)
4364+
TargetEntry *tle = lfirst_node(TargetEntry, tlCell);
4365+
if (!tle->resjunk && tle->resname)
43084366
{
4309-
continue;
4367+
availableCols = lappend(availableCols, tle->resname);
43104368
}
4369+
}
4370+
freeAvailableCols = true;
4371+
}
43114372

4312-
const char *colName = NULL;
4313-
if (colCell != NULL)
4314-
{
4315-
colName = strVal(lfirst(colCell));
4316-
colCell = lnext(colNames, colCell);
4317-
}
4318-
else
4319-
{
4320-
colName = tle->resname;
4321-
}
4373+
/* Walk priority list, return the first match */
4374+
ListCell *tokenCell = NULL;
4375+
foreach(tokenCell, ParsedDistributionColumns)
4376+
{
4377+
const char *candidate = (const char *) lfirst(tokenCell);
43224378

4323-
if (colName != NULL && strcmp(colName, token) == 0)
4379+
ListCell *colCell = NULL;
4380+
foreach(colCell, availableCols)
4381+
{
4382+
const char *colName = (const char *) lfirst(colCell);
4383+
if (pg_strcasecmp(candidate, colName) == 0)
43244384
{
4325-
char *result = pstrdup(token);
4326-
pfree(rawList);
4327-
return result;
4385+
if (freeAvailableCols)
4386+
{
4387+
list_free(availableCols);
4388+
}
4389+
return pstrdup(candidate);
43284390
}
43294391
}
43304392
}
43314393

4332-
pfree(rawList);
4394+
if (freeAvailableCols)
4395+
{
4396+
list_free(availableCols);
4397+
}
4398+
43334399
return NULL;
43344400
}
43354401

4336-
43374402
/*
43384403
* FindMatchingDistributionColumn walks the comma-separated priority list in
43394404
* citus.distribution_columns and returns a palloc'd copy of the first column
@@ -4343,50 +4408,22 @@ FindMatchingDistributionColumnFromTargetList(List *targetList, List *colNames)
43434408
static char *
43444409
FindMatchingDistributionColumn(Oid relationId)
43454410
{
4346-
if (DistributionColumnsGUC == NULL || DistributionColumnsGUC[0] == '\0')
4411+
if (ParsedDistributionColumns == NIL)
43474412
{
43484413
return NULL;
43494414
}
43504415

4351-
/* work on a copy so we don't modify the GUC value */
4352-
char *rawList = pstrdup(DistributionColumnsGUC);
4353-
char *token = NULL;
4354-
char *savePtr = NULL;
4355-
4356-
for (token = strtok_r(rawList, ",", &savePtr);
4357-
token != NULL;
4358-
token = strtok_r(NULL, ",", &savePtr))
4416+
ListCell *cell = NULL;
4417+
foreach(cell, ParsedDistributionColumns)
43594418
{
4360-
/* trim leading whitespace */
4361-
while (*token == ' ' || *token == '\t')
4362-
{
4363-
token++;
4364-
}
4365-
4366-
/* trim trailing whitespace */
4367-
char *end = token + strlen(token) - 1;
4368-
while (end > token && (*end == ' ' || *end == '\t'))
4369-
{
4370-
*end = '\0';
4371-
end--;
4372-
}
4373-
4374-
/* skip empty tokens (e.g. "col1,,col2") */
4375-
if (*token == '\0')
4376-
{
4377-
continue;
4378-
}
4379-
4380-
AttrNumber attNum = get_attnum(relationId, token);
4419+
const char *colName = (const char *) lfirst(cell);
4420+
AttrNumber attNum = get_attnum(relationId, colName);
43814421
if (attNum != InvalidAttrNumber)
43824422
{
4383-
char *result = pstrdup(token);
4384-
pfree(rawList);
4385-
return result;
4423+
return pstrdup(colName);
43864424
}
43874425
}
43884426

4389-
pfree(rawList);
43904427
return NULL;
43914428
}
43924429

@@ -4399,7 +4436,7 @@ FindMatchingDistributionColumn(Oid relationId)
43994436
static bool
44004437
ShouldAutoDistributeNewTable(Oid relationId)
44014438
{
4402-
if (DistributionColumnsGUC == NULL || DistributionColumnsGUC[0] == '\0')
4439+
if (ParsedDistributionColumns == NIL)
44034440
{
44044441
return false;
44054442
}
@@ -4513,7 +4550,7 @@ TryOptimizeCTASForAutoDistribution(CreateTableAsStmt *ctasStmt,
45134550
const char *queryString)
45144551
{
45154552
/* Quick bail-out if the GUC is not set */
4516-
if (DistributionColumnsGUC == NULL || DistributionColumnsGUC[0] == '\0')
4553+
if (ParsedDistributionColumns == NIL)
45174554
{
45184555
return false;
45194556
}
@@ -4608,8 +4645,8 @@ TryOptimizeCTASForAutoDistribution(CreateTableAsStmt *ctasStmt,
46084645
const char *schemaName = into->rel->schemaname;
46094646
const char *tableName = into->rel->relname;
46104647
const char *qualifiedName = schemaName ?
4611-
quote_qualified_identifier(schemaName, tableName) :
4612-
quote_identifier(tableName);
4648+
quote_qualified_identifier(schemaName, tableName) :
4649+
quote_identifier(tableName);
46134650

46144651
/*
46154652
* If IF NOT EXISTS is set and the table already exists, skip.

src/backend/distributed/shared_library_init.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,11 +1260,11 @@ RegisterCitusConfigVariables(void)
12601260
"the table by the first column name that exists in the table. "
12611261
"Applies to CREATE TABLE and CREATE TABLE AS SELECT. "
12621262
"Set to empty string to disable."),
1263-
&DistributionColumnsGUC,
1263+
&DistributionColumns,
12641264
"",
12651265
PGC_USERSET,
12661266
GUC_STANDARD,
1267-
NULL, NULL, NULL);
1267+
CheckDistributionColumns, AssignDistributionColumns, NULL);
12681268

12691269
DefineCustomBoolVariable(
12701270
"citus.enable_alter_database_owner",

src/include/distributed/commands.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626

2727
extern bool AddAllLocalTablesToMetadata;
2828
extern bool EnableSchemaBasedSharding;
29-
extern char *DistributionColumnsGUC;
29+
extern char *DistributionColumns;
30+
extern List *ParsedDistributionColumns;
31+
32+
extern bool CheckDistributionColumns(char **newval, void **extra, GucSource source);
33+
extern void AssignDistributionColumns(const char *newval, void *extra);
3034

3135
/* controlled via GUC, should be accessed via EnableLocalReferenceForeignKeys() */
3236
extern bool EnableLocalReferenceForeignKeys;

0 commit comments

Comments
 (0)