On Wed, Oct 11, 2023 at 12:47 PM Paul Jungwirth <p...@illuminatedcomputing.com> wrote: > > On 9/25/23 14:00, Peter Eisentraut wrote: > > Looking through the tests in v16-0001: > > > > +-- PK with no columns just WITHOUT OVERLAPS: > > +CREATE TABLE temporal_rng ( > > + valid_at tsrange, > > + CONSTRAINT temporal_rng_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS) > > +); > > +ERROR: syntax error at or near "WITHOUT" > > +LINE 3: CONSTRAINT temporal_rng_pk PRIMARY KEY (valid_at WITHOUT OV... > > + ^ > > > > I think this error is confusing. The SQL standard requires at least one > > non-period column in a PK. I don't know why that is or why we should > > implement it. But if we want to implement it, maybe we should enforce > > that in parse analysis rather than directly in the parser, to be able to > > produce a more friendly error message. > > Okay. > > (I think the reason the standard requires one non-period column is to > identify the "entity". If philosophically the row is an Aristotelian > proposition about that thing, the period qualifies it as true just > during some time span. So the scalar part is doing the work that a PK > conventionally does, and the period part does something else. Perhaps a > PK/UNIQUE constraint with no scalar part would still be useful, but not > very often I think, and I'm not sure it makes sense to call it PK/UNIQUE.) > > > +-- PK with a range column/PERIOD that isn't there: > > +CREATE TABLE temporal_rng ( > > + id INTEGER, > > + CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT > > OVERLAPS) > > +); > > +ERROR: range or PERIOD "valid_at" in WITHOUT OVERLAPS does not exist > > > > I think here we should just produce a "column doesn't exist" error > > message, the same as if the "id" column was invalid. We don't need to > > get into the details of what kind of column it should be. That is done > > in the next test > > I'll change it. The reason for the different wording is that it might > not be a column at all. It might be a PERIOD. So what about just "column > or PERIOD doesn't exist"? (Your suggestion is fine too though.) > > > +ERROR: column "valid_at" in WITHOUT OVERLAPS is not a range type > > > > Also, in any case it would be nice to have a location pointer here (for > > both cases). > > Agreed. >
I refactored findNeworOldColumn to better handle error reports. please check the attached.
From 81a5eab2596c0e322dee8165ad73343328f496ed Mon Sep 17 00:00:00 2001 From: pgaddict <jian.universal...@gmail.com> Date: Wed, 25 Oct 2023 13:50:38 +0800 Subject: [PATCH v1 1/1] refactor findNewOrOldColumn. refator findNeworOldColumn to validate WITHOUT OVERLAP constraint associated key column exists and it's a range data type. create/alter table add without overlap constraint: if the key column not exists, it will report the same as no "without overlap" normal case. create table add without overlap constraint: if overlap constraint associated column's data type is not range, it will report it's not a range error, also print out the parser position. alter table add without overlap constraint: if overlap constraint associated column's data type is not range, it will report not a range data type error. it will not print out parser position (I don't know how to do that). --- src/backend/parser/parse_utilcmd.c | 81 ++++++++++--------- .../regress/expected/without_overlaps.out | 8 +- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index e195410c..f44de591 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -124,8 +124,7 @@ static List *get_opclass(Oid opclass, Oid actual_datatype); static void transformIndexConstraints(CreateStmtContext *cxt); static IndexStmt *transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt); -static bool findNewOrOldColumn(CreateStmtContext *cxt, char *colname, char **typname, - Oid *typid); +static void validate_without_overlaps(CreateStmtContext *cxt, char *colname); static void transformExtendedStatistics(CreateStmtContext *cxt); static void transformFKConstraints(CreateStmtContext *cxt, bool skipValidation, @@ -2698,34 +2697,19 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) * Anything in without_overlaps should be included, * but with the overlaps operator (&&) instead of equality. */ - if (constraint->without_overlaps != NULL) { + if (constraint->without_overlaps != NULL) + { char *without_overlaps_str = strVal(constraint->without_overlaps); IndexElem *iparam = makeNode(IndexElem); - char *typname; - Oid typid; /* * Iterate through the table's columns * (like just a little bit above). * If we find one whose name is the same as without_overlaps, * validate that it's a range type. - * - * Otherwise report an error. + * if it cannot found, raise an error. */ - - if (findNewOrOldColumn(cxt, without_overlaps_str, &typname, &typid)) - { - if (!type_is_range(typid)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" in WITHOUT OVERLAPS is not a range type", - without_overlaps_str))); - } - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("range or PERIOD \"%s\" in WITHOUT OVERLAPS does not exist", - without_overlaps_str))); + validate_without_overlaps(cxt, without_overlaps_str); iparam->name = pstrdup(without_overlaps_str); iparam->expr = NULL; @@ -2744,6 +2728,9 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) { /* * Force the column to NOT NULL since it is part of the primary key. + * prior loop foreach(lc, constraint->keys) does not loop over + * without_overlap keys. So we need manually add here. + * */ AlterTableCmd *notnullcmd = makeNode(AlterTableCmd); @@ -2872,31 +2859,37 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) } /* - * Tries to find a column by name among the existing ones (if it's an ALTER TABLE) - * and the new ones. Sets typname and typid if one is found. Returns false if we - * couldn't find a match. + * Tries to find and validate the typid associated + * with a without_overlaps column name, even in ALTER TABLE case. + * After check, without_overlaps column should exist, and it's a range type. + * */ -static bool -findNewOrOldColumn(CreateStmtContext *cxt, char *colname, char **typname, Oid *typid) +static void +validate_without_overlaps(CreateStmtContext *cxt, char *colname) { /* Check the new columns first in case their type is changing. */ - ColumnDef *column = NULL; ListCell *columns; + Oid typid; + bool found = false; foreach(columns, cxt->columns) { column = lfirst_node(ColumnDef, columns); if (strcmp(column->colname, colname) == 0) { - *typid = typenameTypeId(NULL, column->typeName); - *typname = TypeNameToString(column->typeName); - return true; + typid = typenameTypeId(NULL, column->typeName); + if (!type_is_range(typid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" in WITHOUT OVERLAPS is not a range type",colname), + parser_errposition(cxt->pstate, column->location))); + found = true; + break; } } /* Look up columns on existing table. */ - if (cxt->isalter) { Relation rel = cxt->rel; @@ -2905,22 +2898,30 @@ findNewOrOldColumn(CreateStmtContext *cxt, char *colname, char **typname, Oid *t Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i); const char *attname; - if (attr->attisdropped) - continue; - attname = NameStr(attr->attname); if (strcmp(attname, colname) == 0) { - Type type = typeidType(attr->atttypid); - *typid = attr->atttypid; - *typname = pstrdup(typeTypeName(type)); - ReleaseSysCache(type); - return true; + typid = attr->atttypid; + + if (attr->attisdropped) + { + found = false; + break; + } + if (!type_is_range(typid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" in WITHOUT OVERLAPS is not a range type", colname))); + found = true; + break; } } } - return false; + if (!found) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" named in key does not exist",colname))); } /* diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out index 4b8abb08..a916e56a 100644 --- a/src/test/regress/expected/without_overlaps.out +++ b/src/test/regress/expected/without_overlaps.out @@ -19,7 +19,7 @@ CREATE TABLE temporal_rng ( id INTEGER, CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS) ); -ERROR: range or PERIOD "valid_at" in WITHOUT OVERLAPS does not exist +ERROR: column "valid_at" named in key does not exist -- PK with a non-range column: CREATE TABLE temporal_rng ( id INTEGER, @@ -27,6 +27,8 @@ CREATE TABLE temporal_rng ( CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS) ); ERROR: column "valid_at" in WITHOUT OVERLAPS is not a range type +LINE 3: valid_at TEXT, + ^ -- PK with one column plus a range: CREATE TABLE temporal_rng ( -- Since we can't depend on having btree_gist here, @@ -110,7 +112,7 @@ CREATE TABLE temporal_rng2 ( id INTEGER, CONSTRAINT temporal_rng2_uq UNIQUE (id, valid_at WITHOUT OVERLAPS) ); -ERROR: range or PERIOD "valid_at" in WITHOUT OVERLAPS does not exist +ERROR: column "valid_at" named in key does not exist -- UNIQUE with a non-range column: CREATE TABLE temporal_rng2 ( id INTEGER, @@ -118,6 +120,8 @@ CREATE TABLE temporal_rng2 ( CONSTRAINT temporal_rng2_uq UNIQUE (id, valid_at WITHOUT OVERLAPS) ); ERROR: column "valid_at" in WITHOUT OVERLAPS is not a range type +LINE 3: valid_at TEXT, + ^ -- UNIQUE with one column plus a range: CREATE TABLE temporal_rng2 ( -- Since we can't depend on having btree_gist here, -- 2.34.1