[PATCH] Provide support for trailing commas

Started by Greg Sabino Mullane5 days ago11 messages
#1Greg Sabino Mullane
htamfids@gmail.com
1 attachment(s)

tl;dr Provide support for trailing commas, where possible and practical.

(Disclaimer: No LLM or AI was used to craft this patch or this email)

Happy New Year! Please find attached a patch to provide comprehensive
trailing comma support, where we allow (when possible) a comma that does
NOT come before the next item in a list. To wit:

SELECT 1,2,3, FROM pg_class, ORDER BY greatest(relpages,1,);

This is not as trivial a task as it seems, as those who have dabbled with
our parser (or parsers in general) may suspect. At the end of the day,
however, 99% of the places in which we use a comma for a list of items now
have support for trailing commas.

Most of this email is an item by item breakdown of each occurance of commas
with the gram.y file (in order), how it was solved, and a quick example
showing the newly supported syntax.

The overall approach is to break the existing lists into two lists, the new
one with a suffix of "_items", and put the optional trailing comma at the
end of that new list. For example, here's how I solved utility_option_list.
It used to look like this:

utility_option_list:
utility_option_elem { $$ = list_make1($1); }
| utility_option_list ',' utility_option_elem { $$ = lappend($1, $3); }
;

Now it looks like this:

utility_option_list:
utility_option_list_items opt_trailing_comma { $$ = $1; }
;

utility_option_list_items:
utility_option_elem { $$ = list_make1($1); }
| utility_option_list_items ',' utility_option_elem { $$ = lappend($1,
$3); }
;

Where opt_trailing_comma is:

opt_trailing_comma:
',' { $$ = NULL; }
| /* EMPTY */ { $$ = NULL; }
;

That system satisfies most of the grammar, but there were some problematic
spots that caused the ol' shift/reduce conflict that is the bane of those
editing gram.y.

First, I made our commas a little more special with:

%left ','

Second, I added some new items to the %nonassoc lists. They are detailed
below as added, but the basic need is because lists of things in Postgres
often have some optional modifiers. For example, TRUNCATE takes a list of
table to truncate, but also allows the CASCADE keyword. Further, we do not
prevent people from creating a table named "cascade", so this command will
truncate two tables:

TRUNCATE TABLE foobar, cascade;

This command will truncate one table, and cascade to other tables as needed:

TRUNCATE TBALE foobar cascade;

Trailing comma allows us to call a list of tables, and end with a comma:

TRUNCATE TBALE foobar, foobaz, ;

However, we now have some ambiguity (i.e. shift/reduce conflicts) because
"TRUNCATE TABLE foobar, cascade;" can now indicate truncating two tables,
or one table (with a trailing comma), plus the cascade keyword. This is
solved by this patch in favor of the latter interpretation. In other words,
when there is a trailing comma, the list is not greedy.

Luckily, the number of places needing such tweaks is small. There are a few
places where trailing comma support was warranted too technically difficult
and/or not worth the trouble - they are all documented below.

Each section below includes an approximate line number, simply to help
reviewers find the location easier, as it is a big file. I've not
consolidated all of the test cases into a single regression test, but am
open to the idea (I'm not convinced it is needed).

Cheers,
Greg

=======
* utility_option_list

Status: fully supported

Line: 1160

Used by: CHECKPOINT, REINDEX, CLUSTER, VACUUM, ANALYZE, EXPLAIN

Test cases (should fail on head, but work with this patch):

EXPLAIN (verbose, format yaml, ) select 1;

ANALYZE (skip_locked,verbose,) pg_class;

=======
* var_list

Status: not supported

Line: 1854

Used by generic_set, but there a lot of shift/reduce conflicts. For example:

alter function foo() set search_path = public, abc immutable;

alter function foo() set search_path = public, abc, immutable;

If we allow trailing commas, would immutable be the name of the schema or
the SET modifier?

In theory we could create a new list to allow trailing commas on simpler
items, e.g.:

alter system set log_destination = stderr,csvlog,;

But I feel like this is going too far, so I left this one alone.

=======
* alter_table_cmds

Status: fully supported

Line: 2386

Used by ALTER TABLE to support multiple actions.
Also ALTER INDEX, ALTER SEQUENCE, ALTER VIEW, ALTER MATERIALIZED VIEW,
ALTER FOREIGN TABLE

Test case:

CREATE TABLE t(id int);
ALTER TABLE t ADD foo INT, ADD bar INT,;

=======
* reloption_list

Status: fully supported

Line: 3192

Used by: reloptions and opt_reloptions to support SET() items for ALTER
TABLE,
CREATE INDEX, CREATE VIEW, and a few others.

Test cases:

ALTER TABLE foo SET (fillfactor=50,);

CREATE INDEX i1 ON t(id) WITH (fillfactor=50, deduplicate_items=off,);

=======
* hash_partbound

Status: fully supported

Line: 3363

Used by: partitioning FOR VALUES WITH ()

Test cases:

CREATE TABLE p(id INT) PARTITION BY hash(id);

CREATE TABLE c PARTITION OF p FOR VALUES WITH (remainder 1, modulus 4,);

=======
* alter_type_cmds

Status: fully supported

Line: 3394

Used by: ALTER TYPE foo xxx

TEST CASE:

CREATE TYPE mytype AS (id int);
ALTER TYPE mytype ADD attribute id2 int, ADD attribute id3 int, ;

=======
* copy_generic_opt_list

Line: 3678

Used by: COPY ... WITH ()

Test case:

COPY pg_language(oid,lanname) TO stdout WITH (header, format csv,);

=======
* copy_generic_opt_arg_list

Status: fully supported

Line: 3705

Used by: COPY WITH ( force_quote( [, ...] ) )

Test case:

COPY pg_language(oid, lanname) TO stdout WITH (format csv,
force_quote(oid,lanname, ) );

=======
* TableElementList

Status: fully supported

Line: 3898

Test case:

CREATE TEMP TABLE foo(id int,);

=======
* TypedTableElementList

Status: fully supported

Line: 3908

Test case:

CREATE TYPE abc AS (id int, id2 int);
CREATE TABLE mytype OF abc (id default 1, id2 default 3,);

=======
* columnList

Status: partially supported

Line: 4542

In order:

1. UNIQUE constraints and PRIMARY KEY

Needed to modify opt_without_overlaps to add the optional comma directly:

opt_without_overlaps:
',' { $$ = false; }
| WITHOUT OVERLAPS { $$ = true; }
| ',' WITHOUT OVERLAPS { $$ = true; }
| /*EMPTY*/ { $$ = false; }
;

Test case:

CREATE TEMP TABLE t (c1 int4range, c2 int4range);
ALTER TABLE t ADD CONSTRAINT test1 UNIQUE (c1, c2);
ALTER TABLE t ADD CONSTRAINT test2 UNIQUE (c1, c2,);
ALTER TABLE t ADD CONSTRAINT test3 UNIQUE (c1, c2 WITHOUT OVERLAPS);
ALTER TABLE t ADD CONSTRAINT test4 UNIQUE (c1, c2, WITHOUT OVERLAPS);
ALTER TABLE t ADD PRIMARY KEY (c1, c2, );

2. FOREIGN KEY

As above, but we modify optionalPeriodName to allow a leading comma.

Also add %nonassoc PERIOD

optionalPeriodName:
',' { $$ = NULL; }
| PERIOD columnElem { $$ = $3; }
| ',' PERIOD columnElem { $$ = $3; }
| /*EMPTY*/ { $$ = NULL; }
;

Test case:

CREATE TEMP TABLE t (id INT PRIMARY KEY);
CREATE TEMP TABLE t2 (id INT,
CONSTRAINT tempo1 FOREIGN KEY(id,) REFERENCES t (id,));

CREATE temp TABLE t3 (id INT4RANGE, valid_at DATERANGE,
CONSTRAINT t3_pk PRIMARY KEY (id, valid_at, WITHOUT OVERLAPS));

CREATE temp TABLE t4 (id INT4RANGE, valid_at DATERANGE,
CONSTRAINT t4_fk FOREIGN KEY (id, PERIOD valid_at)
REFERENCES t3 (id, PERIOD valid_at));

3. opt_column_list

Many sub-items for this

3.1 CopyStmt

Test case:

COPY pg_language (lanowner,) TO stdout;

3.2 copy_opt_item FORCE_QUOTE

Test case:

COPY pg_language (lanowner) TO stdout WITH (format csv, FORCE_QUOTE
(lanowner,) );

3.3 copy_opt_item FORCE_NULL

Test case:

CREATE TEMP TABLE T (foo TEXT);
COPY t FROM program 'echo abc' WITH (format csv, FORCE_NULL (foo, ) );

3.4 ColConstraintElem REFERENCES

Test case:

CREATE TEMP TABLE t (id INT PRIMARY KEY);
CREATE TEMP TABLE t2 (id INT CONSTRAINT tempo REFERENCES t (id,));

3.5 key_action (e.g. SET NULL)

Test case:

CREATE TEMP TABLE t (id INT PRIMARY KEY);
CREATE TEMP TABLE t2 (id INT CONSTRAINT tempo REFERENCES t (id) ON DELETE
SET NULL (id,) );

3.6 create_as_target

Test case:

CREATE TEMP TABLE t (id,) AS select 123;

3.7 create_mv_target:

Test case:

CREATE MATERIALIZED VIEW t(id,) AS select 123;
DROP MATERIALIZED VIEW t;

3.8 privileges

Test case:

CREATE TEMP TABLE t (id INT);
GRANT SELECT (id, ) ON TABLE t TO public;

3.9 PublicationObjSpec

Test case:

CREATE TABLE t(id INT);
CREATE TABLE t2(id INT);
CREATE PUBLICATION p FOR TABLE t(id,);
DROP PUBLICATION p;
CREATE PUBLICATION p FOR TABLE t *, ONLY t2 (id,);
DROP PUBLICATION p;
DROP TABLE t;

3.10 ViewStmt:

Test case:

CREATE TEMP VIEW t (id, ) AS select 123;

4. opt_column_and_period_list: '(' columnList optionalPeriodName ')'

Test case: see #3 "FOREIGN KEY" above

5. opt_c_include: INCLUDE '(' columnListOptionalComma ')'

Test case:

CREATE TEMP TABLE t (id INT, id2 INT);
ALTER TABLE t ADD CONSTRAINT u1 UNIQUE (id) INCLUDE (id2,);

6. TriggerOneEvent

Test case:

CREATE TEMP TABLE t (id int, id2 int);
CREATE FUNCTION f() returns trigger language plpgsql as 'begin return
new; end';
CREATE TRIGGER tr1 after UPDATE OF id, ON t for each row execute function
f();
drop function f() cascade;

7. privileges for columns

Test case:

CREATE TEMP TABLE t(id int);
GRANT ALL (id, ) on table t to public;

8. ViewStmt

Test case:

CREATE recursive VIEW v (id,) as select 123;

9. opt_search_clause

Test case:

WITH RECURSIVE x AS (select 123 as id union all select * from x)
SEARCH DEPTH FIRST BY id, SET foo select * from x limit 1;

Also added a "depth_or_breadth" to refactor the opt_search_clause first

10. opt_cycle_clause

Test case:

WITH RECURSIVE x AS (select 123 as id union all select * from x)
CYCLE id, SET id2 to 3 default 1 using id3 select * from x limit 1;

End of columnList!

=======
* ExclusionConstraintList

Status: fully supported

Line: 4585

Used by: EXCLUDE

Done by: adding ExclusionConstraintListItems

Test case:

CREATE TEMP TABLE tt(id int, EXCLUDE (id WITH =), );

=======
* part_params

Status: fully supported

Line: 4739

Used by: PartitionSpect etc. - only when creating partitioned tables

Done by: adding part_params_items

Test case:

CREATE TEMP TABLE t(id int) PARTITION BY hash (id,);

=======
* stats_params

Status: fully supported

Line: 4859

Used by: create statistics

Done by: adding stats_params_items

Test case:

CREATE TEMP TABLE t1(id int, id2 int);
CREATE STATISTICS ON id, id2, FROM t1;

=======
* NumericOnly_list

Status: not supported

Line; 5188

Only used to set permissions on large objects, not worth the effort

=======
* generic_option_list

Status: fully supported

Line: 5628

Used by: create_generic_options for FDW items, e.g.
CREATE FOREIGN DATA WRAPPER
CREATE SERVER
CREATE FOREIGN TABLE
IMPORT FOREIGN SCHEMA
CREATE USER MAPPING

Done by: adding generic_option_list_items

Test case:

CREATE EXTENSION if not exists postgres_fdw;
DROP SERVER if exists testserver CASCADE;
CREATE SERVER testserver foreign data wrapper postgres_fdw OPTIONS
(dbname 'foo',);
CREATE USER MAPPING for postgres SERVER testserver OPTIONS (user 'alice',
);
CREATE FOREIGN TABLE foo(c1 int) SERVER testserver OPTIONS (schema_name
'bob',);
DROP SERVER testserver CASCADE;

=======
* alter_generic_option_list

Status: fully supported

Line: 5644

Used by: similar to above, but modifying FDW-related objects

Done by: adding alter_generic_option_list_items

Test case:

CREATE EXTENSION if not exists postgres_fdw;
DROP SERVER if exists testserver CASCADE;
CREATE SERVER testserver foreign data wrapper postgres_fdw OPTIONS
(dbname 'foo');

ALTER SERVER testserver OPTIONS (SET dbname 'foo2', );

DROP SERVER testserver CASCADE;

=======
* TriggerFuncArgs

Status: fully supported

Line: 6329

Used by: arguments to functions, naturally

Done by: adding TriggerFuncArgsItems

Test case:

CREATE TEMP TABLE t (id int);
DROP FUNCTION if exists footrig() CASCADE;
CREATE FUNCTION footrig() returns trigger language plpgsql as 'begin
return new; end';
CREATE TRIGGER tr after insert on t for each row execute function
footrig(1,2,);

=======
* event_trigger_value_list:

Line: 6438

Status: fully supported

Used by: tags for event trigger creations

Done by: adding event_trigger_value_list_items

Test case:

DROP FUNCTION if exists foo() CASCADE;
CREATE FUNCTION foo() returns event_trigger language plpgsql as 'begin
return;end';
CREATE EVENT TRIGGER tfoo on sql_drop when tag in ('DROP TABLE',) execute
function foo();
DROP FUNCTION foo() CASCADE;

=======
* def_list

Status: fully supported

Line: 6661

Used by:
"definition":
create aggregate, operator, collation, type, search *
alter publication, alter subscription
alter search dictionary
"opt_definition":
column constraint unique, primary key, exclude
create publication, create subscription, alter subscription

Done by: adding def_list_items

Test case:

DROP OPERATOR if exists === (date,date);
CREATE OPERATOR === ( leftarg=date, rightarg=date, function=date_eq, );
DROP OPERATOR === (date,date);

CREATE TEMP TABLE t (id int, UNIQUE(id) WITH (fillfactor=42, ) );

DROP PUBLICATION if exists foo;
CREATE PUBLICATION foo WITH (publish = 'insert', );
DROP PUBLICATION foo;

=======
* old_aggr_list

Status: not supported

Line: 6680

Extremely old syntax for CREATE AGGREGATE, no need to support this.

=======
* enum_val_list

Status: fully supported

Line: 6707

Used by: enums!

Done by: adding enum_val_list_items

Test case:

CREATE TYPE fooe AS ENUM ('foo', 'bar', );
DROP TYPE fooe;

=======
* opcast_item_list

Status: fully supported

Line: 6830

Used by: create operator class and alter operator family (add)

Done by: adding opcast_item_list_items

Test case:

DROP OPERATOR CLASS if exists oc USING gist;
CREATE OPERATOR CLASS oc FOR TYPE int USING gist AS function 1 pi(), ;
DROP OPERATOR CLASS oc USING gist;

DROP OPERATOR FAMILY if exists ofam USING gist;
CREATE OPERATOR FAMILY ofam USING gist;
ALTER OPERATOR FAMILY ofam USING gist ADD operator 1 = (int, int), ;
DROP OPERATOR FAMILY ofam USING gist;

=======
* opclass_drop_list

Status: fully supported

Line: 6936

Used by: alter operator family (drop)

Done by: adding opclass_drop_list_items

Test case:

DROP OPERATOR FAMILY if exists ofam USING gin;
CREATE OPERATOR FAMILY ofam USING gin;
ALTER OPERATOR FAMILY ofam USING gin ADD operator 1 = (int, int);
ALTER OPERATOR FAMILY ofam USING gin DROP operator 1 (int, int), ;
DROP OPERATOR FAMILY ofam USING gin;

=======
* any_name_list

Status: fully supported

Line: 7236

Used by: privileges, text search configuration, DROP

1. privilege_target

Test case:

CREATE DOMAIN testd1 AS int;
CREATE DOMAIN testd2 AS int;
GRANT usage ON DOMAIN testd1, testd2, TO public;
DROP DOMAIN testd1, testd2;

2. privilege_target

Test case:

CREATE TEMP TABLE foo1 (id int);
CREATE TEMP TABLE foo2 (id int);
GRANT usage ON TYPE foo1, foo2, TO public;

3. AlterTSConfigurationStmt

Test case:

CREATE TEXT SEARCH CONFIGURATION tsc ( copy = simple );
ALTER TEXT SEARCH CONFIGURATION tsc ADD MAPPING FOR tag, blank, WITH
simple;
DROP TEXT SEARCH CONFIGURATION tsc;

4. DropStmt

Test Case:

CREATE TEMP TABLE foo(id int);
DROP TABLE foo, cascade;

=======
* type_name_list

Status: fully supported

Line: 7251

Used by: multiple "type" items

Done by: adding type_name_list_items

CREATE TEMP TABLE foo1 (id int);
CREATE TYPE foo AS (id int);
DROP TYPE foo, cascade;

=======
* privilege_list

Status: fully supported

Line: 7894

Used by: all grant and revoke variants

Done by: adding privilege_list_items

Test case:

CREATE TEMP TABLE t1 (id int);
GRANT select, insert, ON t1 TO public;
REVOKE select, insert, ON t1 FROM public;

=======
* parameter_name_list

Status: fully supported

Line: 7940

Used by: permissions on parameters

Done by: adding parameter_name_list_items

Test case:

GRANT SET ON PARAMETER work_mem, TO public;

=======
* grantee_list

Status: fully supported

Line: 8158

Used by: GRANT to a list of roles

Done by: adding grantee_list_items, %nonassoc GRANTED

Test case:

CREATE USER alice;
CREATE TEMP TABLE t1 (id int);
GRANT select ON TABLE t1 TO alice, alice,;
GRANT select ON TABLE t1 TO alice, alice, WITH GRANT OPTION;
GRANT select ON TABLE t1 TO alice, alice, GRANTED BY current_user;

Because GRANT has two optional items at the end of it:
opt_grant_grant_option opt_granted_by

We need to ensure that 'WITH' and 'GRANTED' have some extra stickiness. The
former
already has it, but GRANTED was added to the %nonassoc list around line 898

=======
* grant_role_opt_list

Status: fully supported

Line: 8235

Used by: options when adding one role to another

Done by: adding grant_role_opt_list_items, %nonassoc GRANTED

Test case:

CREATE USER alice;
CREATE ROLE commarole;
GRANT commarole TO alice WITH admin true, inherit false,;
GRANT commarole TO alice WITH admin option, inherit true, GRANTED BY
current_user;

=======
* index_params

Status: fully supported

Line: 8439

Used by: list of columns when creating an index

Done by: adding index_params_items

Test case:

CREATE TEMP TABLE t (id int);
CREATE INDEX ti1 ON t USING btree (id,);

=======
* index_including_params

Status: fully supported

Line: 8502

Used by: list of columns for covering indexes

Done by: adding index_including_params_items

Test case:

CREATE TEMP TABLE t (id int, email text);
CREATE INDEX ti1 ON t (id) INCLUDE (email, );

=======
* func_args_list

Status: fully supported

Line: 8603

Used by: certain places where we need to reference a function by its args

Done by: adding func_args_list_items

Test case:

CREATE FUNCTION commatest(int) returns int language sql as 'select 1';
COMMENT ON FUNCTION commatest(int,) IS 'Welcome, extra commas!';
DROP FUNCTION commatest(int, );

=======
* function_with_argtypes_list

Status: fully supported

Line: 8613

Used by: dropping multiple functions at once, assigning privs to multiples

Done by: adding function_with_argtypes_list_items, %nonassoc CASCADE
RESTRICT

Test case:

CREATE FUNCTION commatest(int) returns int language sql as 'select 1';
DROP FUNCTION commatest(int),;
DROP FUNCTION if exists commatest(int), CASCADE;

=======
* func_args_with_defaults_list:

Status: fully supported

Line: 8665

Used by: list of args when doing a create function only

Done by: adding func_args_with_defaults_list_items

Test case:

CREATE FUNCTION commatest(int, int, ) returns int language sql as '
select 42 ';

=======
* aggr_args_list

Status: fully supported

Line: 8864

Used by: aggregate declaration

Done by: adding aggr_args_list_items

Test case:

CREATE AGGREGATE foo (int, ) (sfunc=gcd, stype=int);
DROP AGGREGATE foo(int,);

=======
* aggregate_with_argtypes_list

Status: fully supported

Line: 8881

Used by: operations on multiple aggregates at once

Done by: adding aggregate_with_argtypes_list_items, %nonassoc CASCADE
RESTRICT

Test case:

CREATE AGGREGATE foo1 (int) (sfunc=gcd, stype=int);
CREATE AGGREGATE foo2 (int) (sfunc=gcd, stype=int);
DROP AGGREGATE foo1(int), foo2(int), ;

=======
* func_as

Status: not supported

Line: 9044

Putting this in for completelness, no trailing comma support is needed, as
there are only two items

=======
* transform_type_list

Status: fully supported

Line: 9053

Used by: functions that transform types

Done by: adding transform_type_list_items

Test case:

CREATE TRANSFORM FOR int LANGUAGE sql (from sql with function
time_support(internal) );
CREATE FUNCTION foo() returns int language sql
transform for type int, for type int4, as 'select 1';
DROP TRANSFORM FOR int language sql cascade;

=======
* table_func_column_list

Status: fully supported

Line: 9076

Used by: list of columns for a table-returning function

Done by: adding table_func_column_list_items

Test case:

CREATE FUNCTION foo() RETURNS table(x int,) language sql as 'select 1';
DROP FUNCTION foo();

=======
* oper_argtypes

Status: not supported

Line: 9323

Used by: arguments to operators ("left" and "right")

No support for trailing commas needed, unless we want to get really pedantic

=======
* operator_with_argtypes_list

Status: fully supported

Line: 9293

Used by: dropping multiple operators at once

Done by: adding operator_with_argtypes_list_items, %nonassoc CASCADE
RESTRICT

Test case:

CREATE OPERATOR === ( leftarg=date, rightarg=date, function=date_eq);
CREATE OPERATOR ==== ( leftarg=date, rightarg=date, function=date_eq);
DROP OPERATOR === (date,date), ==== (date, date), ;

=======
* operator_def_list

Status: fully supported

Line: 10521

Used by: modifying an existing operator

Done by: adding operator_def_list_items

Test case:

CREATE OPERATOR === ( leftarg=date, rightarg=date, function=date_eq);
ALTER OPERATOR === (date, date) SET (hashes, );
DROP OPERATOR === (date, date);

=======
* pub_obj_list

Status: fully supported

Line: 10929

Used by: create and alter publications

Done by: adding pub_obj_list_items

Test case:

CREATE TABLE foo1 (id int);
CREATE TABLE foo2 (id int);
CREATE PUBLICATION p FOR TABLE foo1, table foo2, ;
DROP PUBLICATION p;
DROP TABLE foo1, foo2;

=======
* pub_obj_type_list

Status: fully supported

Line: 10950

Used by: create publication (as of v19 only)

Done by: adding pub_obj_type_list_items (also cleaned up nearby indentation
issue)

Test case:

CREATE PUBLICATION p FOR ALL TABLES, ALL SEQUENCES;
DROP PUBLICATION p;

=======
* notify_payload

Status: not supported

Line: 11335

Used by: notify with a channel plus a payload

Seems not needed, given that this is not really a list of items per se,
just an optional payload.

=======
* transaction_mode_list

Status: fully supported

Line: 11462

Used by: set transaction

Done by: adding transaction_mode_list_items

Test case:

BEGIN WORK deferrable, isolation level serializable, ; ROLLBACK;

=======
* drop_option_list

Status: fully supported

Line: 11756

Used by: drop database only

Done by: adding drop_option_list_items

Test case:

DROP DATABASE bob with (force,force,);

Only option right now is "force" but this is for future-proofing things.

=======
* vacuum_relation_list

Status: fully supported

Line: 12199

Used by: vacuum multiple things at once

Done by: adding vacuum_relation_list_items

Test case:

VACUUM pg_am, pg_proc, ;

=======
* insert_column_list

Status: fully supported

Line: 12480

Used by: your basic insert statement

Done by: adding insert_column_list_items

Test case:

CREATE TEMP TABLE t (id int);
INSERT INTO t(id,) VALUES (1);

=======
* returning_options

Status: not supported

Line: 12641

Used by: insert that uses RETURNING WITH

Does not seem worth it, as we only support OLD and NEW

=======
* set_clause_list

Status: fully supported

Line: 12686

Used by: UPDATE .. SET

Done by: adding set_clause_list_items

Test case:

CREATE TEMP TABLE t (id int);
UPDATE t SET id = 1, WHERE id <> 0;

=======
* set_target_list

Status: fully supported

Line: 12731

Used by: SET a bunch of things at once inside parens

Done by: adding set_target_list_items

Test case:

CREATE TEMP TABLE t (id int);
UPDATE t SET (id,) = ROW(1);

=======
* cte_list

Status: not supported

Line: 13287

Used by: chaining multiple CTEs together

This one is not likely to be supported, there is way too much ambiguity.

=======
* sortby_list

Status: partially supported

Line: 13407

Used by: ORDER BY and json_array_aggregate_order_by_clause_opt

Done by: adding sortby_list_items and a new sortby_list_no_trailing_comma

Test case:

SELECT * FROM pg_language ORDER BY 1,2,3, LIMIT 1;

This one needed to be split into two versions, as the "normal" ORDER BY did
just fine with adding a comma at the end of the list. The json aggregate
however, spit up shift/reduce errors. That's a rather niche usage of
ORDER BY, so at the end of the day, I decided to support the 99% usage and
leave the json one alone.

=======
* group_by_list

Status: fully supported

Line: 13782

Used by: group by, of course

Done by: adding group_by_list_items

Test case:

SELECT datname, count(*) FROM pg_database GROUP BY 1, ;

=======
* from_list

Status: fully supported

Line: 13786

Used by: list of tables in a from clause

Done by: adding from_list_items

Test case:

SELECT count(*) FROM pg_am, pg_proc, ;

=======
* relation_expr_list

Status: fully supported

Line: 14144

Used by: import foreign schema, truncate tables, lock tables

Done by: adding relation_expr_list_items, %nonassoc RESTART CONTINUE_P

Not completely happy about having to add two more keywords just to
support TRUNCATE, but TRUNCATE is a rather important command, so
I think it is worth it.

Test case:

CREATE TEMP TABLE t1 (id int);
CREATE TEMP TABLE t2 (id int);
TRUNCATE TABLE t1, t2, ;
TRUNCATE TABLE t1, t2, CONTINUE IDENTITY;
LOCK TABLE pg_am, pg_proc, NOWAIT;

There is a little bit of ambiguity there as there is a very weak use case
for doing what that LOCK TABLE above used to do: apply the default lock
mode to three tables, one of which is named "nowait". But "nowait" is
a dumb name for a table, so I think caveat emptor applies here. The same
thing applies to some of the other examples, e.g. having a table named
"CASCADE")

=======
* rowsfrom_list

Status: fully supported

Line: 14243

Used by: rows from a list - see below

Done by: adding rowsfrom_list_items

Test case:

SELECT * FROM ROWS FROM ( abs(1), abs(2), );

=======
* TableFuncElementList

Status: fully supported

Line: 14283

Used by: aliases for functions with declared list of columns

Done by: adding TableFuncElementListItems

Test case:

SELECT * FROM jsonb_to_record('{"foo": 10}') AS x (foo int, );

=======
* xmltable_column_list

Status: fully supported

Line: 14343

Used by: The weird xmltable function

Done by: adding xmltable_column_list_items

Test case:

WITH xmldata(data) AS (VALUES
('<aa xmlns="https://xmlsux&quot;&gt;&lt;item foo="42" /></aa>'::xml))
SELECT xmltable.* FROM XMLTABLE(xmlnamespaces('https://xmlsux&#39; AS x),
'/x:aa/x:item' passing (select data from xmldata)
COLUMNS foo int path '@foo', );

=======
* xml_namespace_list

Status: fully supported

Line: 14460

Used by: xml stuff

Done by: adding xml_namespace_list_items

Test case:

WITH xmldata(data) AS (VALUES
('<aa xmlns="https://xmlsux&quot;&gt;&lt;item foo="42" /></aa>'::xml))
SELECT xmltable.* FROM XMLTABLE(xmlnamespaces('https://xmlsux&#39; AS x, ),
'/x:aa/x:item' passing (select data from xmldata)
COLUMNS foo int path '@foo' );

=======
* json_table_column_definition_list:

Status: fully supported

Line: 14518

Used by:

Done by: adding json_table_column_definition_list_items

Test case:

SELECT * from json_table('{"id": 123}', '$[*]' columns (id int path
'$.id',));

=======
* xml_attribute_list

Status: fully supported

Line: 16480

Used by: XML stuff

Done by: adding xml_attribute_list_items

Test case:

SELECT xmlforest(1 as is, );

=======
* window_definition_list

Status: fully supported

Line: 16832

Used by:

Done by:

Test case:

SELECT 1 from pg_database window foo as (partition by oid), ;

=======
* expr_list

Status: partially supported

Line: 16903

Used by: lots of things - will break down each one

Done by: adding expr_list_items, plus expr_list_no_trailing_comma

1. list and range partition definitions

Supported. Test case:

CREATE TEMP TABLE t1 (id int) partition by list (id);
CREATE TEMP TABLE t2 partition of t1 for values in (1,2,);
CREATE TEMP TABLE t3 (id int, id2 int) partition by range (id, id2);
CREATE TEMP TABLE t4 partition of t3 for values from (1,2,) to (6,7,);

2. execute parameters

Supported. Test case:

PREPARE foo as select $1;
EXECUTE foo (1, );
DEALLOCATE foo;

3. merge values

Supported. Test case:

CREATE TEMP TABLE t (id int);
MERGE INTO t USING (values (1)) on (true) when not matched then insert
(id) values (42,);

4. distinct on

Supported. Test case:

SELECT distinct on (prolang,) prolang from pg_proc;

5. rollup and cube

Supported. Test case:

SELECT relkind, relnamespace, count(*) from pg_class group by
rollup(1,2,);
SELECT relkind, relnamespace, count(*) from pg_class group by cube(1,2,);

6. values

Supported. Test case:

SELECT * FROM (values(1,2,), (3,4,) );

7. tablesample

Supported. Test case:

SELECT relname from pg_class tablesample bernoulli (1,);

Yes, I know bernoulli only accepts a single arg anyway.

8. generic type modifiers

Supported. Test case:

SELECT 123::numeric(10, );

9. bit type with a length

Supported. Test case:

SELECT 42::bit(8,);

10. general expression foo in (list) and not in (list)

Supported. Test case:

SELECT 1 IN (1,2,3,);
SELECT 1 NOT IN (1,2,3,);

11. grouping

Supported. Test case:

SELECT relkind, GROUPING(relkind,), count(*) from pg_class group by 1;

12. coalesce, greatest, least

Supported. Test case:

SELECT COALESCE(1,2, );
SELECT GREATEST(42, 24,);
SELECT LEAST(42, 24,);

13. xmlconcat, xmlelement

Supported. Test case:

SELECT XMLCONCAT('1'::xml, '2'::xml, );
SELECT XMLELEMENT(name foo, 'fizz', 'buzz',);

14. partition by inside a window

Supported. Test case:

SELECT lag(oid) over(PARTITION BY 1,2,) from pg_am;

15. ROW

PARTIALLY supported. ROW is a tricky case. In the end, I was only able to
reliably get the explicit_row to work with a hard-coded comma variant. On
the
other hand, that's a pretty common form, so I'm happy overall.

Test case:

SELECT ROW(1,2,3,);
/* Still fails: SELECT (1,2,3,); */

16. array lists

Supported. Test case:

SELECT ARRAY[1,2,3,];

17. trim list

Supported. Test case:

SELECT TRIM(both from 'abba', 'a', );

Not particularly useful, but it's kinda built-in with the expr_list.

This is the end of expr_list!

=======
* func_arg_list

Not to be confused with func_args_list, way back around line 8600!

Status: partially supported

Line: 17036

Used by: arguments to functions

Done by: func_arg_list_items, func_arg_list_no_trailing_comma

Test case:

/* Works for simple variants: */
CREATE PROCEDURE foo(int) language sql as 'select 1';
CALL foo(1,);
CALL foo(ALL 1,);
DROP PROCEDURE foo(int);

This is only partly supported, due to the weird way in which we can call
functions,
but I think the main cases are covered well enough.

=======
* type_list

Status: fully supported

Line: 16954

Used by: prepare arguments

Done by: adding type_list_items

Test case:

PREPARE foo(int,) AS select $1;

=======
* array_expr_list

Status: fully supported

Line: 16972

Used by: multiple array items

Done by: adding array_expr_list_items

Test case:

SELECT ARRAY[ [1],[2], ];

=======
* json_arguments

Status: not supported

Line: 17334

Test case:

SELECT JSON_VALUE(jsonb '[]', '$' PASSING 1 as x, 2 as y,);

Only used in a relatively obscure command; lots of shift/reduce conflicts;
not worth the effort

=======
* json_name_and_value_list

Status: not supported

Line: 17496

Test case:

SELECT json_object(1:2,3:4,);

As above, too many shift/reduce conflicts as there are many post-comma
opttions.

=======
* json_value_expr_list:

Status: not supported

Line: 17529

Test case:

SELECT json_array(1,2,3,);

Continuing our json trend, we cannot support this one either

=======
* target_list

Status: fully supported

Line: 17456

Used by: select a list of things

Done by: adding target_list_items

Test case:

SELECT 1,2,3, ;

=======
* qualified_name_list

Status: fully supported

Line: 17507

Used by:

Done by: adding qualified_name_list_items, %nonassoc DEFERRED IMMEDIATE
SKIP NOWAIT

This important clause was a little tricky and requires some %nonassoc magic

1. set constraints (see ConstraintsSetStmt)

The full syntax is:
SET CONSTRAINTS constraints_set_list constraints_set_mode

where contraints_set_list contains our qualified name list, and the mode
can be
DEFERRED or IMMEDIATE. So we add those two as %nonassoc

Test case:

CREATE TEMP TABLE foo(x int, y int);
ALTER TABLE foo add constraint xplus check(x > 0), add constraint yplus
check(y > 0);
BEGIN;
SET CONSTRAINTS xplus, yplus, immediate;
ROLLBACK;

2. table inheritance list (see CreateStmt)

Test case:

CREATE TEMP TABLE foo() INHERITS (pg_proc, );

3. grant/revoke list of items (see privilege_target)

Test case:

GRANT select on table pg_class, to public;

4. Locking multiple objects (see for_locking_item)

Test case:

SELECT 1 from pg_database, pg_am for update of pg_database nowait limit 1;
SELECT 1 from pg_database, pg_am for update of pg_database, nowait limit
1;
SELECT 1 from pg_database, pg_am for update of pg_database, pg_am nowait
limit 1;
SELECT 1 from pg_database, pg_am for update of pg_database, pg_am, nowait
limit 1;

Since the list here is followed by some optional items, we needed
to add those to %nonassoc: SKIP NOWAIT

=======
* name_list

Status: fully supported

Line: 17701

Used by: tons of things that need, well, a list of names

Done by: adding name_list_items, %nonassoc RESTRICT CASCADE REVOKE REPLACE

Test case:

GRANT USAGE on schema public, TO public;

=======
* role_list

Status: fully supported

Line: 17753

Used by: list of roles (e.g. to grant multiple roles at once)

Done by: adding role_list_items, plus large %nonassoc list:
CONNECTION INHERIT ENCRYPTED ADMIN PASSWORD ROLE SYSID UNENCRYPTED VALID

Test case:

GRANT pg_monitor TO alice, ;

I don't like adding so many %nonassoc for just this one simple case, but I
think
it is warranted as a list of role is pretty common.

That's the end of the list! Congratulations on reaching this far, even if
you
simply scrolled to the bottom without reading everything! :) The total
number of
changes was not too bad, just spread out a lot:

$ git log --oneline -1 --shortstat
95a599b62a7 (HEAD -> comma,commma,commma,comma,chameleon, master) Provide
support for trailing commas, where possible and practical.
1 file changed, 564 insertions(+), 305 deletions(-)

Attachments:

0001-Provide-support-for-trailing-commas-where-possible-and-practical.patchapplication/x-patch; name=0001-Provide-support-for-trailing-commas-where-possible-and-practical.patchDownload
From 43573e88ae1c9833daf0fb2ad07bf65491bc9900 Mon Sep 17 00:00:00 2001
From: Greg Sabino Mullane <greg@turnstep.com>
Date: Tue, 6 Jan 2026 12:27:35 -0500
Subject: [PATCH] 
 Provide-support-for-trailing-commas-where-possible-and-practical

---
 src/backend/parser/gram.y | 868 ++++++++++++++++++++++++--------------
 1 file changed, 563 insertions(+), 305 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 713ee5c10a2..02d3a623703 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -321,13 +321,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				simple_select values_clause
 				PLpgSQL_Expr PLAssignStmt
 
+%type <node>        opt_trailing_comma
 %type <str>			opt_single_name
 %type <list>		opt_qualified_name
 %type <boolean>		opt_concurrently
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
 %type <list>		opt_wait_with_clause
-%type <list>		utility_option_list
+%type <list>		utility_option_list utility_option_list_items
 %type <defelt>		utility_option_elem
 %type <str>			utility_option_name
 %type <node>		utility_option_arg
@@ -337,14 +338,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 	   replica_identity partition_cmd index_partition_cmd
-%type <list>	alter_table_cmds alter_type_cmds
+%type <list>	alter_table_cmds alter_table_cmds_items alter_type_cmds alter_type_cmds_items
 %type <list>    alter_identity_column_option_list
 %type <defelt>  alter_identity_column_option
 %type <node>	set_statistics_value
 %type <str>		set_access_method_name
 
 %type <list>	createdb_opt_list createdb_opt_items copy_opt_list
-				transaction_mode_list
+				transaction_mode_list transaction_mode_list_items
 				create_extension_opt_list alter_extension_opt_list
 %type <defelt>	createdb_opt_item copy_opt_item
 				transaction_mode_item
@@ -356,7 +357,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				opt_grant_grant_option
 				opt_nowait opt_if_exists opt_with_data
 				opt_transaction_chain
-%type <list>	grant_role_opt_list
+%type <list>	grant_role_opt_list grant_role_opt_list_items
 %type <defelt>	grant_role_opt
 %type <node>	grant_role_opt_value
 %type <ival>	opt_nowait_or_skip
@@ -369,7 +370,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_in_database
 
 %type <str>		parameter_name
-%type <list>	OptSchemaEltList parameter_name_list
+%type <list>	OptSchemaEltList parameter_name_list parameter_name_list_items
 
 %type <chr>		am_type
 
@@ -382,7 +383,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>	TransitionRowOrTable TransitionOldOrNew
 %type <node>	TriggerTransition
 
-%type <list>	event_trigger_when_list event_trigger_value_list
+%type <list>	event_trigger_when_list event_trigger_value_list event_trigger_value_list_items
 %type <defelt>	event_trigger_when_item
 %type <chr>		enable_trigger
 
@@ -406,12 +407,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		iso_level opt_encoding
 %type <rolespec> grantee
-%type <list>	grantee_list
+%type <list>	grantee_list grantee_list_items
 %type <accesspriv> privilege
-%type <list>	privileges privilege_list
+%type <list>	privileges privilege_list privilege_list_items
 %type <privtarget> privilege_target
 %type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
 %type <list>	function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
+%type <list>	function_with_argtypes_list_items aggregate_with_argtypes_list_items
+%type <list>	operator_with_argtypes_list_items
 %type <ival>	defacl_privilege_target
 %type <defelt>	DefACLOption
 %type <list>	DefACLOptionList
@@ -421,47 +424,54 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <selectlimit> opt_select_limit select_limit limit_clause
 
 %type <list>	parse_toplevel stmtmulti routine_body_stmt_list
-				OptTableElementList TableElementList OptInherit definition
-				OptTypedTableElementList TypedTableElementList
+				OptTableElementList TableElementList TableElementListItems OptInherit definition
+				OptTypedTableElementList TypedTableElementList TypedTableElementListItems
 				reloptions opt_reloptions
-				OptWith opt_definition func_args func_args_list
-				func_args_with_defaults func_args_with_defaults_list
-				aggr_args aggr_args_list
+				OptWith opt_definition func_args func_args_list func_args_list_items
+				func_args_with_defaults func_args_with_defaults_list func_args_with_defaults_list_items
+				aggr_args aggr_args_list aggr_args_list_items
 				func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
 				old_aggr_definition old_aggr_list
 				oper_argtypes RuleActionList RuleActionMulti
-				opt_column_list columnList opt_name_list
-				sort_clause opt_sort_clause sortby_list index_params
-				stats_params
-				opt_include opt_c_include index_including_params
-				name_list role_list from_clause from_list opt_array_bounds
-				qualified_name_list any_name any_name_list type_name_list
-				any_operator expr_list attrs
+				opt_column_list columnList columnListOptionalComma columnListItems opt_name_list
+				sort_clause opt_sort_clause sortby_list sortby_list_items sortby_list_no_trailing_comma
+				index_params index_params_items
+				stats_params stats_params_items
+				opt_include opt_c_include index_including_params index_including_params_items
+				name_list name_list_items role_list role_list_items
+				from_clause from_list from_list_items opt_array_bounds
+				qualified_name_list qualified_name_list_items any_name any_name_list any_name_list_items
+				type_name_list type_name_list_items
+				any_operator expr_list expr_list_items expr_list_no_trailing_comma attrs
 				distinct_clause opt_distinct_clause
-				target_list opt_target_list insert_column_list set_target_list
-				merge_values_clause
-				set_clause_list set_clause
-				def_list operator_def_list indirection opt_indirection
-				reloption_list TriggerFuncArgs opclass_item_list opclass_drop_list
+				target_list target_list_items opt_target_list insert_column_list insert_column_list_items
+				set_target_list	set_target_list_items merge_values_clause
+				set_clause_list set_clause_list_items set_clause
+				def_list def_list_items operator_def_list operator_def_list_items
+				indirection opt_indirection
+				reloption_list reloption_list_items TriggerFuncArgs TriggerFuncArgsItems
+				opclass_item_list opclass_item_list_items opclass_drop_list opclass_drop_list_items
 				opclass_purpose opt_opfamily transaction_mode_list_or_empty
-				OptTableFuncElementList TableFuncElementList opt_type_modifiers
+				OptTableFuncElementList TableFuncElementList TableFuncElementListItems opt_type_modifiers
 				prep_type_clause
 				execute_param_clause using_clause
 				returning_with_clause returning_options
-				opt_enum_val_list enum_val_list table_func_column_list
+				opt_enum_val_list enum_val_list enum_val_list_items
+				table_func_column_list table_func_column_list_items
 				create_generic_options alter_generic_options
-				relation_expr_list dostmt_opt_list
-				transform_element_list transform_type_list
+				relation_expr_list relation_expr_list_items dostmt_opt_list
+				transform_element_list transform_type_list transform_type_list_items
 				TriggerTransitions TriggerReferencing
-				vacuum_relation_list opt_vacuum_relation_list
-				drop_option_list pub_obj_list pub_all_obj_type_list
+				vacuum_relation_list vacuum_relation_list_items opt_vacuum_relation_list
+				drop_option_list drop_option_list_items
+				pub_obj_list pub_obj_list_items pub_all_obj_type_list pub_all_obj_type_list_items
 
 %type <retclause> returning_clause
 %type <node>	returning_option
 %type <retoptionkind> returning_option_kind
 %type <node>	opt_routine_body
 %type <groupclause> group_clause
-%type <list>	group_by_list
+%type <list>	group_by_list group_by_list_items
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
 %type <node>	grouping_sets_clause
 
@@ -534,15 +544,16 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				columnref having_clause func_table xmltable array_expr
 				OptWhereClause operator_def_arg
 %type <list>	opt_column_and_period_list
-%type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
+%type <list>	rowsfrom_item rowsfrom_list rowsfrom_list_items opt_col_def_list
 %type <boolean> opt_ordinality opt_without_overlaps
-%type <list>	ExclusionConstraintList ExclusionConstraintElem
-%type <list>	func_arg_list func_arg_list_opt
+%type <list>	ExclusionConstraintList ExclusionConstraintListItems ExclusionConstraintElem
+%type <list>	func_arg_list func_arg_list_no_trailing_comma func_arg_list_items func_arg_list_opt
 %type <node>	func_arg_expr
-%type <list>	row explicit_row implicit_row type_list array_expr_list
+%type <list>	row explicit_row implicit_row type_list type_list_items array_expr_list array_expr_list_items
 %type <node>	case_expr case_arg when_clause case_default
 %type <list>	when_clause_list
 %type <node>	opt_search_clause opt_cycle_clause
+%type <boolean> depth_or_breadth
 %type <ival>	sub_type opt_materialized
 %type <node>	NumericOnly
 %type <list>	NumericOnly_list
@@ -562,13 +573,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
-%type <list>	generic_option_list alter_generic_option_list
+%type <list>	generic_option_list generic_option_list_items alter_generic_option_list alter_generic_option_list_items
 
 %type <ival>	reindex_target_relation reindex_target_all
 
 %type <node>	copy_generic_opt_arg copy_generic_opt_arg_list_item
 %type <defelt>	copy_generic_opt_elem
-%type <list>	copy_generic_opt_list copy_generic_opt_arg_list
+%type <list>	copy_generic_opt_list copy_generic_opt_list_items copy_generic_opt_arg_list copy_generic_opt_arg_list_items
 %type <list>	copy_options
 
 %type <typnam>	Typename SimpleTypename ConstTypename
@@ -618,15 +629,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_provider security_label
 
 %type <target>	xml_attribute_el
-%type <list>	xml_attribute_list xml_attributes
+%type <list>	xml_attribute_list xml_attribute_list_items xml_attributes
 %type <node>	xml_root_version opt_xml_root_standalone
 %type <node>	xmlexists_argument
 %type <ival>	document_or_content
 %type <boolean>	xml_indent_option xml_whitespace_option
-%type <list>	xmltable_column_list xmltable_column_option_list
+%type <list>	xmltable_column_list xmltable_column_list_items xmltable_column_option_list
 %type <node>	xmltable_column_el
 %type <defelt>	xmltable_column_option_el
-%type <list>	xml_namespace_list
+%type <list>	xml_namespace_list xml_namespace_list_items
 %type <target>	xml_namespace_el
 
 %type <node>	func_application func_expr_common_subexpr
@@ -637,7 +648,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <list>	within_group_clause
 %type <node>	filter_clause
-%type <list>	window_clause window_definition_list opt_partition_clause
+%type <list>	window_clause window_definition_list window_definition_list_items opt_partition_clause
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
 %type <ival>	null_treatment opt_window_exclusion_clause
@@ -647,11 +658,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	generated_when override_kind opt_virtual_or_stored
 %type <partspec>	PartitionSpec OptPartitionSpec
 %type <partelem>	part_elem
-%type <list>		part_params
+%type <list>		part_params part_params_items
 %type <partboundspec> PartitionBoundSpec
 %type <singlepartspec>	SinglePartitionSpec
 %type <list>		partitions_list
-%type <list>		hash_partbound
+%type <list>		hash_partbound hash_partbound_elem_items
 %type <defelt>		hash_partbound_elem
 
 %type <node>	json_format_clause
@@ -672,7 +683,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_arguments
 				json_behavior_clause_opt
 				json_passing_clause_opt
-				json_table_column_definition_list
+				json_table_column_definition_list json_table_column_definition_list_items
 %type <str>		json_table_path_name_opt
 %type <ival>	json_behavior_type
 				json_predicate_type_constraint
@@ -891,6 +902,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
+%nonassoc	DEFERRED IMMEDIATE SKIP NOWAIT RESTRICT CASCADE REVOKE REPLACE PERIOD GRANTED
+%nonassoc	RESTART CONTINUE_P
+%nonassoc	ADMIN CONNECTION INHERIT ENCRYPTED PASSWORD ROLE SYSID UNENCRYPTED VALID
 %left		Op OPERATOR		/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
@@ -903,6 +917,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		','
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -1131,6 +1146,11 @@ stmt:
 /*
  * Generic supporting productions for DDL
  */
+opt_trailing_comma:
+			 ','							{ $$ = NULL; }
+			| /* EMPTY */					{ $$ = NULL; }
+			;
+
 opt_single_name:
 			ColId							{ $$ = $1; }
 			| /* EMPTY */					{ $$ = NULL; }
@@ -1158,14 +1178,12 @@ opt_utility_option_list:
 		;
 
 utility_option_list:
-			utility_option_elem
-				{
-					$$ = list_make1($1);
-				}
-			| utility_option_list ',' utility_option_elem
-				{
-					$$ = lappend($1, $3);
-				}
+			utility_option_list_items opt_trailing_comma 			{ $$ = $1; }
+		;
+
+utility_option_list_items:
+			utility_option_elem										{ $$ = list_make1($1); }
+			| utility_option_list_items ',' utility_option_elem		{ $$ = lappend($1, $3); }
 		;
 
 utility_option_elem:
@@ -2384,8 +2402,12 @@ AlterTableStmt:
 		;
 
 alter_table_cmds:
-			alter_table_cmd							{ $$ = list_make1($1); }
-			| alter_table_cmds ',' alter_table_cmd	{ $$ = lappend($1, $3); }
+			alter_table_cmds_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+alter_table_cmds_items:
+			alter_table_cmd									{ $$ = list_make1($1); }
+			| alter_table_cmds_items ',' alter_table_cmd	{ $$ = lappend($1, $3); }
 		;
 
 partitions_list:
@@ -3190,8 +3212,12 @@ opt_reloptions:		WITH reloptions					{ $$ = $2; }
 		;
 
 reloption_list:
-			reloption_elem							{ $$ = list_make1($1); }
-			| reloption_list ',' reloption_elem		{ $$ = lappend($1, $3); }
+			reloption_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+reloption_list_items:
+			reloption_elem								{ $$ = list_make1($1); }
+			| reloption_list_items ',' reloption_elem	{ $$ = lappend($1,$3); }
 		;
 
 /* This should match def_elem and also allow qualified names */
@@ -3361,14 +3387,12 @@ hash_partbound_elem:
 		;
 
 hash_partbound:
-		hash_partbound_elem
-			{
-				$$ = list_make1($1);
-			}
-		| hash_partbound ',' hash_partbound_elem
-			{
-				$$ = lappend($1, $3);
-			}
+			hash_partbound_elem_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+hash_partbound_elem_items:
+			hash_partbound_elem									{ $$ = list_make1($1); }
+			| hash_partbound_elem_items ',' hash_partbound_elem	{ $$ = lappend($1,$3); }
 		;
 
 /*****************************************************************************
@@ -3392,8 +3416,12 @@ AlterCompositeTypeStmt:
 			;
 
 alter_type_cmds:
-			alter_type_cmd							{ $$ = list_make1($1); }
-			| alter_type_cmds ',' alter_type_cmd	{ $$ = lappend($1, $3); }
+			alter_type_cmds_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+alter_type_cmds_items:
+			alter_type_cmd								{ $$ = list_make1($1); }
+			| alter_type_cmds_items ',' alter_type_cmd	{ $$ = lappend($1, $3); }
 		;
 
 alter_type_cmd:
@@ -3676,14 +3704,12 @@ opt_using:
 
 /* new COPY option syntax */
 copy_generic_opt_list:
-			copy_generic_opt_elem
-				{
-					$$ = list_make1($1);
-				}
-			| copy_generic_opt_list ',' copy_generic_opt_elem
-				{
-					$$ = lappend($1, $3);
-				}
+			copy_generic_opt_list_items opt_trailing_comma			{ $$ = $1; }
+			;
+
+copy_generic_opt_list_items:
+			copy_generic_opt_elem									{ $$ = list_make1($1); }
+			| copy_generic_opt_list_items ',' copy_generic_opt_elem	{ $$ = lappend($1, $3); }
 		;
 
 copy_generic_opt_elem:
@@ -3703,14 +3729,14 @@ copy_generic_opt_arg:
 		;
 
 copy_generic_opt_arg_list:
-			  copy_generic_opt_arg_list_item
-				{
-					$$ = list_make1($1);
-				}
-			| copy_generic_opt_arg_list ',' copy_generic_opt_arg_list_item
-				{
-					$$ = lappend($1, $3);
-				}
+			copy_generic_opt_arg_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+copy_generic_opt_arg_list_items:
+			copy_generic_opt_arg_list_item
+																{ $$ = list_make1($1); }
+			| copy_generic_opt_arg_list_items ',' copy_generic_opt_arg_list_item
+																{ $$ = lappend($1,$3); }
 		;
 
 /* beware of emitting non-string list elements here; see commands/define.c */
@@ -3896,25 +3922,21 @@ OptTypedTableElementList:
 		;
 
 TableElementList:
-			TableElement
-				{
-					$$ = list_make1($1);
-				}
-			| TableElementList ',' TableElement
-				{
-					$$ = lappend($1, $3);
-				}
+			TableElementListItems opt_trailing_comma	{ $$ = $1; }
+		;
+
+TableElementListItems:
+			TableElement								{ $$ = list_make1($1); }
+			| TableElementListItems ',' TableElement	{ $$ = lappend($1, $3); }
 		;
 
 TypedTableElementList:
-			TypedTableElement
-				{
-					$$ = list_make1($1);
-				}
-			| TypedTableElementList ',' TypedTableElement
-				{
-					$$ = lappend($1, $3);
-				}
+			TypedTableElementListItems opt_trailing_comma		{ $$ = $1; }
+		;
+
+TypedTableElementListItems:
+			TypedTableElement									{ $$ = list_make1($1); }
+			| TypedTableElementListItems ',' TypedTableElement	{ $$ = lappend($1,$3); }
 		;
 
 TableElement:
@@ -4526,12 +4548,14 @@ opt_no_inherit:	NO INHERIT							{  $$ = true; }
 		;
 
 opt_without_overlaps:
-			WITHOUT OVERLAPS						{ $$ = true; }
+			','										{ $$ = false; }
+			| ',' WITHOUT OVERLAPS					{ $$ = true; }
+			| WITHOUT OVERLAPS						{ $$ = true; }
 			| /*EMPTY*/								{ $$ = false; }
 	;
 
 opt_column_list:
-			'(' columnList ')'						{ $$ = $2; }
+			'(' columnListOptionalComma ')'						{ $$ = $2; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
@@ -4540,9 +4564,20 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+columnListOptionalComma:
+			columnListItems opt_trailing_comma		{ $$ = $1; }
+		;
+
+columnListItems:
+			columnElem								{ $$ = list_make1($1); }
+			| columnListItems ',' columnElem		{ $$ = lappend($1,$3); }
+		;
+
 optionalPeriodName:
-			',' PERIOD columnElem { $$ = $3; }
-			| /*EMPTY*/               { $$ = NULL; }
+			','						{ $$ = NULL; }
+			| PERIOD columnElem		{ $$ = $2; }
+			| ',' PERIOD columnElem	{ $$ = $3; }
+			| /*EMPTY*/				{ $$ = NULL; }
 	;
 
 opt_column_and_period_list:
@@ -4556,7 +4591,7 @@ columnElem: ColId
 				}
 		;
 
-opt_c_include:	INCLUDE '(' columnList ')'			{ $$ = $3; }
+opt_c_include:	INCLUDE '(' columnListOptionalComma ')'			{ $$ = $3; }
 			 |		/* EMPTY */						{ $$ = NIL; }
 		;
 
@@ -4583,9 +4618,13 @@ key_match:  MATCH FULL
 		;
 
 ExclusionConstraintList:
-			ExclusionConstraintElem					{ $$ = list_make1($1); }
-			| ExclusionConstraintList ',' ExclusionConstraintElem
-													{ $$ = lappend($1, $3); }
+			ExclusionConstraintListItems 					{ $$ = $1; }
+		;
+
+ExclusionConstraintListItems:
+			ExclusionConstraintElem 						{ $$ = list_make1($1); }
+			| ExclusionConstraintListItems ',' ExclusionConstraintElem
+															{ $$ = lappend($1, $3); }
 		;
 
 ExclusionConstraintElem: index_elem WITH any_operator
@@ -4737,8 +4776,13 @@ PartitionSpec: PARTITION BY ColId '(' part_params ')'
 				}
 		;
 
-part_params:	part_elem						{ $$ = list_make1($1); }
-			| part_params ',' part_elem			{ $$ = lappend($1, $3); }
+part_params:
+			part_params_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+part_params_items:
+			part_elem								{ $$ = list_make1($1); }
+			| part_params_items ',' part_elem		{ $$ = lappend($1, $3); }
 		;
 
 part_elem: ColId opt_collate opt_qualified_name
@@ -4857,8 +4901,13 @@ CreateStatsStmt:
  * written without parens.
  */
 
-stats_params:	stats_param							{ $$ = list_make1($1); }
-			| stats_params ',' stats_param			{ $$ = lappend($1, $3); }
+stats_params:
+			stats_params_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+stats_params_items:
+			stats_param								{ $$ = list_make1($1); }
+			| stats_params_items ',' stats_param	{ $$ = lappend($1, $3); }
 		;
 
 stats_param:	ColId
@@ -5626,14 +5675,12 @@ create_generic_options:
 		;
 
 generic_option_list:
-			generic_option_elem
-				{
-					$$ = list_make1($1);
-				}
-			| generic_option_list ',' generic_option_elem
-				{
-					$$ = lappend($1, $3);
-				}
+			generic_option_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+generic_option_list_items:
+			generic_option_elem									{ $$ = list_make1($1); }
+			| generic_option_list_items ',' generic_option_elem	{ $$ = lappend($1, $3); }
 		;
 
 /* Options definition for ALTER FDW, SERVER and USER MAPPING */
@@ -5642,14 +5689,12 @@ alter_generic_options:
 		;
 
 alter_generic_option_list:
-			alter_generic_option_elem
-				{
-					$$ = list_make1($1);
-				}
-			| alter_generic_option_list ',' alter_generic_option_elem
-				{
-					$$ = lappend($1, $3);
-				}
+			alter_generic_option_list_items opt_trailing_comma				{ $$ = $1; }
+		;
+
+alter_generic_option_list_items:
+			alter_generic_option_elem										{ $$ = list_make1($1); }
+			| alter_generic_option_list_items ',' alter_generic_option_elem	{ $$ = lappend($1, $3); }
 		;
 
 alter_generic_option_elem:
@@ -6241,7 +6286,7 @@ TriggerOneEvent:
 				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_DELETE), NIL); }
 			| UPDATE
 				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_UPDATE), NIL); }
-			| UPDATE OF columnList
+			| UPDATE OF columnListOptionalComma
 				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_UPDATE), $3); }
 			| TRUNCATE
 				{ $$ = list_make2(makeInteger(TRIGGER_TYPE_TRUNCATE), NIL); }
@@ -6327,9 +6372,13 @@ FUNCTION_or_PROCEDURE:
 		;
 
 TriggerFuncArgs:
-			TriggerFuncArg							{ $$ = list_make1($1); }
-			| TriggerFuncArgs ',' TriggerFuncArg	{ $$ = lappend($1, $3); }
-			| /*EMPTY*/								{ $$ = NIL; }
+			TriggerFuncArgsItems opt_trailing_comma		{ $$ = $1; }
+		;
+
+TriggerFuncArgsItems:
+			TriggerFuncArg								{ $$ = list_make1($1); }
+			| TriggerFuncArgsItems ',' TriggerFuncArg	{ $$ = lappend($1,$3); }
+			| /*EMPTY*/									{ $$ = NIL; }
 		;
 
 TriggerFuncArg:
@@ -6436,10 +6485,12 @@ event_trigger_when_item:
 		;
 
 event_trigger_value_list:
-		  SCONST
-			{ $$ = list_make1(makeString($1)); }
-		| event_trigger_value_list ',' SCONST
-			{ $$ = lappend($1, makeString($3)); }
+			event_trigger_value_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+event_trigger_value_list_items:
+			SCONST												{ $$ = list_make1(makeString($1)); }
+			| event_trigger_value_list_items ',' SCONST			{ $$ = lappend($1, makeString($3)); }
 		;
 
 AlterEventTrigStmt:
@@ -6659,8 +6710,13 @@ DefineStmt:
 definition: '(' def_list ')'						{ $$ = $2; }
 		;
 
-def_list:	def_elem								{ $$ = list_make1($1); }
-			| def_list ',' def_elem					{ $$ = lappend($1, $3); }
+def_list:
+			 def_list_items opt_trailing_comma	{ $$ = $1; }
+	;
+
+def_list_items:
+			def_elem			{ $$ = list_make1($1); }
+			| def_list_items ',' def_elem	{ $$ = lappend($1, $3); }
 		;
 
 def_elem:	ColLabel '=' def_arg
@@ -6705,10 +6761,13 @@ opt_enum_val_list:
 		| /*EMPTY*/								{ $$ = NIL; }
 		;
 
-enum_val_list:	Sconst
-				{ $$ = list_make1(makeString($1)); }
-			| enum_val_list ',' Sconst
-				{ $$ = lappend($1, makeString($3)); }
+enum_val_list:
+			enum_val_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+enum_val_list_items:
+			Sconst								{ $$ = list_make1(makeString($1)); }
+			| enum_val_list_items ',' Sconst	{ $$ = lappend($1, makeString($3)); }
 		;
 
 /*****************************************************************************
@@ -6828,8 +6887,12 @@ CreateOpClassStmt:
 		;
 
 opclass_item_list:
-			opclass_item							{ $$ = list_make1($1); }
-			| opclass_item_list ',' opclass_item	{ $$ = lappend($1, $3); }
+			opclass_item_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+opclass_item_list_items:
+			opclass_item								{ $$ = list_make1($1); }
+			| opclass_item_list_items ',' opclass_item	{ $$ = lappend($1, $3); }
 		;
 
 opclass_item:
@@ -6934,8 +6997,12 @@ AlterOpFamilyStmt:
 		;
 
 opclass_drop_list:
-			opclass_drop							{ $$ = list_make1($1); }
-			| opclass_drop_list ',' opclass_drop	{ $$ = lappend($1, $3); }
+			opclass_drop_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+opclass_drop_list_items:
+			opclass_drop								{ $$ = list_make1($1); }
+			| opclass_drop_list_items ',' opclass_drop	{ $$ = lappend($1, $3); }
 		;
 
 opclass_drop:
@@ -7234,8 +7301,12 @@ object_type_name_on_any_name:
 		;
 
 any_name_list:
+			any_name_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+any_name_list_items:
 			any_name								{ $$ = list_make1($1); }
-			| any_name_list ',' any_name			{ $$ = lappend($1, $3); }
+			| any_name_list_items ',' any_name		{ $$ = lappend($1, $3); }
 		;
 
 any_name:	ColId						{ $$ = list_make1(makeString($1)); }
@@ -7249,8 +7320,12 @@ attrs:		'.' attr_name
 		;
 
 type_name_list:
-			Typename								{ $$ = list_make1($1); }
-			| type_name_list ',' Typename			{ $$ = lappend($1, $3); }
+			type_name_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+type_name_list_items:
+			Typename									{ $$ = list_make1($1); }
+			| type_name_list_items ',' Typename			{ $$ = lappend($1, $3); }
 		;
 
 /*****************************************************************************
@@ -7874,7 +7949,7 @@ privileges: privilege_list
 				{ $$ = NIL; }
 			| ALL PRIVILEGES
 				{ $$ = NIL; }
-			| ALL '(' columnList ')'
+			| ALL '(' columnListOptionalComma ')'
 				{
 					AccessPriv *n = makeNode(AccessPriv);
 
@@ -7882,7 +7957,7 @@ privileges: privilege_list
 					n->cols = $3;
 					$$ = list_make1(n);
 				}
-			| ALL PRIVILEGES '(' columnList ')'
+			| ALL PRIVILEGES '(' columnListOptionalComma ')'
 				{
 					AccessPriv *n = makeNode(AccessPriv);
 
@@ -7892,8 +7967,13 @@ privileges: privilege_list
 				}
 		;
 
-privilege_list:	privilege							{ $$ = list_make1($1); }
-			| privilege_list ',' privilege			{ $$ = lappend($1, $3); }
+privilege_list:
+			privilege_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+privilege_list_items:
+			privilege								{ $$ = list_make1($1); }
+			| privilege_list_items ',' privilege	{ $$ = lappend($1, $3); }
 		;
 
 privilege:	SELECT opt_column_list
@@ -7938,14 +8018,12 @@ privilege:	SELECT opt_column_list
 		;
 
 parameter_name_list:
-		parameter_name
-			{
-				$$ = list_make1(makeString($1));
-			}
-		| parameter_name_list ',' parameter_name
-			{
-				$$ = lappend($1, makeString($3));
-			}
+			parameter_name_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+parameter_name_list_items:
+			parameter_name									{ $$ = list_make1(makeString($1)); }
+			| parameter_name_list_items ',' parameter_name	{ $$ = lappend($1, makeString($3)); }
 		;
 
 parameter_name:
@@ -8156,8 +8234,12 @@ privilege_target:
 
 
 grantee_list:
+			grantee_list_items opt_trailing_comma { $$ = $1; }
+		;
+
+grantee_list_items:
 			grantee									{ $$ = list_make1($1); }
-			| grantee_list ',' grantee				{ $$ = lappend($1, $3); }
+			| grantee_list_items ',' grantee				{ $$ = lappend($1, $3); }
 		;
 
 grantee:
@@ -8233,8 +8315,12 @@ RevokeRoleStmt:
 		;
 
 grant_role_opt_list:
-			grant_role_opt_list ',' grant_role_opt	{ $$ = lappend($1, $3); }
-			| grant_role_opt						{ $$ = list_make1($1); }
+			grant_role_opt_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+grant_role_opt_list_items:
+			grant_role_opt									{ $$ = list_make1($1); }
+			| grant_role_opt_list_items ',' grant_role_opt	{ $$ = lappend($1, $3); }
 		;
 
 grant_role_opt:
@@ -8437,10 +8523,14 @@ access_method_clause:
 			| /*EMPTY*/								{ $$ = DEFAULT_INDEX_TYPE; }
 		;
 
-index_params:	index_elem							{ $$ = list_make1($1); }
-			| index_params ',' index_elem			{ $$ = lappend($1, $3); }
+index_params:
+			index_params_items opt_trailing_comma		{ $$ = $1; }
 		;
 
+index_params_items:
+			index_elem									{ $$ = list_make1($1); }
+			| index_params_items ',' index_elem			{ $$ = lappend($1, $3); }
+		;
 
 index_elem_options:
 	opt_collate opt_qualified_name opt_asc_desc opt_nulls_order
@@ -8500,8 +8590,13 @@ opt_include:		INCLUDE '(' index_including_params ')'			{ $$ = $3; }
 			 |		/* EMPTY */						{ $$ = NIL; }
 		;
 
-index_including_params:	index_elem						{ $$ = list_make1($1); }
-			| index_including_params ',' index_elem		{ $$ = lappend($1, $3); }
+index_including_params:
+			index_including_params_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+index_including_params_items:
+			index_elem										{ $$ = list_make1($1); }
+			| index_including_params_items ',' index_elem	{ $$ = lappend($1, $3); }
 		;
 
 opt_collate: COLLATE any_name						{ $$ = $2; }
@@ -8601,13 +8696,22 @@ func_args:	'(' func_args_list ')'					{ $$ = $2; }
 		;
 
 func_args_list:
+			func_args_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+func_args_list_items:
 			func_arg								{ $$ = list_make1($1); }
-			| func_args_list ',' func_arg			{ $$ = lappend($1, $3); }
+			| func_args_list_items ',' func_arg	   	{ $$ = lappend($1, $3); }
 		;
 
 function_with_argtypes_list:
+			function_with_argtypes_list_items opt_trailing_comma
+													{ $$ = $1; }
+		;
+
+function_with_argtypes_list_items:
 			function_with_argtypes					{ $$ = list_make1($1); }
-			| function_with_argtypes_list ',' function_with_argtypes
+			| function_with_argtypes_list_items ',' function_with_argtypes
 													{ $$ = lappend($1, $3); }
 		;
 
@@ -8663,8 +8767,13 @@ func_args_with_defaults:
 		;
 
 func_args_with_defaults_list:
-		func_arg_with_default						{ $$ = list_make1($1); }
-		| func_args_with_defaults_list ',' func_arg_with_default
+			func_args_with_defaults_list_items opt_trailing_comma
+													{ $$ = $1; }
+		;
+
+func_args_with_defaults_list_items:
+			func_arg_with_default				   	{ $$ = list_make1($1); }
+			| func_args_with_defaults_list_items ',' func_arg_with_default
 													{ $$ = lappend($1, $3); }
 		;
 
@@ -8862,8 +8971,12 @@ aggr_args:	'(' '*' ')'
 		;
 
 aggr_args_list:
+			aggr_args_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+aggr_args_list_items:
 			aggr_arg								{ $$ = list_make1($1); }
-			| aggr_args_list ',' aggr_arg			{ $$ = lappend($1, $3); }
+			| aggr_args_list_items ',' aggr_arg		{ $$ = lappend($1, $3); }
 		;
 
 aggregate_with_argtypes:
@@ -8879,8 +8992,13 @@ aggregate_with_argtypes:
 		;
 
 aggregate_with_argtypes_list:
+			aggregate_with_argtypes_list_items opt_trailing_comma
+													{ $$ = $1; }
+		;
+
+aggregate_with_argtypes_list_items:
 			aggregate_with_argtypes					{ $$ = list_make1($1); }
-			| aggregate_with_argtypes_list ',' aggregate_with_argtypes
+			| aggregate_with_argtypes_list_items ',' aggregate_with_argtypes
 													{ $$ = lappend($1, $3); }
 		;
 
@@ -9051,8 +9169,14 @@ routine_body_stmt:
 		;
 
 transform_type_list:
-			FOR TYPE_P Typename { $$ = list_make1($3); }
-			| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
+			transform_type_list_items opt_trailing_comma
+												{ $$ = $1; }
+		;
+
+transform_type_list_items:
+			FOR TYPE_P Typename					{ $$ = list_make1($3); }
+			| transform_type_list_items ',' FOR TYPE_P Typename
+												{ $$ = lappend($1, $5); }
 		;
 
 opt_definition:
@@ -9074,14 +9198,13 @@ table_func_column:	param_name func_type
 		;
 
 table_func_column_list:
-			table_func_column
-				{
-					$$ = list_make1($1);
-				}
-			| table_func_column_list ',' table_func_column
-				{
-					$$ = lappend($1, $3);
-				}
+			table_func_column_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+table_func_column_list_items:
+			table_func_column		{ $$ = list_make1($1); }
+			| table_func_column_list_items ',' table_func_column
+									{ $$ = lappend($1, $3); }
 		;
 
 /*****************************************************************************
@@ -9291,8 +9414,13 @@ any_operator:
 		;
 
 operator_with_argtypes_list:
+			operator_with_argtypes_list_items opt_trailing_comma
+													{ $$ = $1; }
+		;
+
+operator_with_argtypes_list_items:
 			operator_with_argtypes					{ $$ = list_make1($1); }
-			| operator_with_argtypes_list ',' operator_with_argtypes
+			| operator_with_argtypes_list_items ',' operator_with_argtypes
 													{ $$ = lappend($1, $3); }
 		;
 
@@ -10519,8 +10647,13 @@ AlterOperatorStmt:
 				}
 		;
 
-operator_def_list:	operator_def_elem								{ $$ = list_make1($1); }
-			| operator_def_list ',' operator_def_elem				{ $$ = lappend($1, $3); }
+operator_def_list:
+			operator_def_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+operator_def_list_items:
+			operator_def_elem								{ $$ = list_make1($1); }
+			| operator_def_list_items ',' operator_def_elem	{ $$ = lappend($1, $3); }
 		;
 
 operator_def_elem: ColLabel '=' NONE
@@ -10927,11 +11060,15 @@ PublicationObjSpec:
 				}
 				;
 
-pub_obj_list:	PublicationObjSpec
-					{ $$ = list_make1($1); }
-			| pub_obj_list ',' PublicationObjSpec
-					{ $$ = lappend($1, $3); }
-	;
+pub_obj_list:
+			pub_obj_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+pub_obj_list_items:
+			PublicationObjSpec							{ $$ = list_make1($1); }
+			| pub_obj_list_items ',' PublicationObjSpec	{ $$ = lappend($1, $3); }
+		;
+
 
 PublicationAllObjSpec:
 				ALL TABLES
@@ -10946,13 +11083,16 @@ PublicationAllObjSpec:
 						$$->pubobjtype = PUBLICATION_ALL_SEQUENCES;
 						$$->location = @1;
 					}
-					;
+		;
 
-pub_all_obj_type_list:	PublicationAllObjSpec
-					{ $$ = list_make1($1); }
-				| pub_all_obj_type_list ',' PublicationAllObjSpec
-					{ $$ = lappend($1, $3); }
-	;
+pub_all_obj_type_list:
+			pub_all_obj_type_list_items opt_trailing_comma			{ $$ = $1; }
+		;
+
+pub_all_obj_type_list_items:
+			PublicationAllObjSpec									{ $$ = list_make1($1); }
+			| pub_all_obj_type_list_items ',' PublicationAllObjSpec	{ $$ = lappend($1, $3); }
+		;
 
 
 /*****************************************************************************
@@ -11460,14 +11600,14 @@ transaction_mode_item:
 									   makeIntConst(false, @1), @1); }
 		;
 
-/* Syntax with commas is SQL-spec, without commas is Postgres historical */
 transaction_mode_list:
-			transaction_mode_item
-					{ $$ = list_make1($1); }
-			| transaction_mode_list ',' transaction_mode_item
-					{ $$ = lappend($1, $3); }
-			| transaction_mode_list transaction_mode_item
-					{ $$ = lappend($1, $2); }
+			transaction_mode_list_items opt_trailing_comma			{ $$ = $1; }
+		;
+
+transaction_mode_list_items:
+			transaction_mode_item									{ $$ = list_make1($1); }
+			| transaction_mode_list_items ',' transaction_mode_item	{ $$ = lappend($1, $3); }
+			| transaction_mode_list_items transaction_mode_item		{ $$ = lappend($1, $2); }
 		;
 
 transaction_mode_list_or_empty:
@@ -11519,7 +11659,7 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
 					n->withCheckOption = $11;
 					$$ = (Node *) n;
 				}
-		| CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
+		| CREATE OptTemp RECURSIVE VIEW qualified_name '(' columnListOptionalComma ')' opt_reloptions
 				AS SelectStmt opt_check_option
 				{
 					ViewStmt   *n = makeNode(ViewStmt);
@@ -11538,7 +11678,7 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list opt_reloptions
 								 parser_errposition(@12)));
 					$$ = (Node *) n;
 				}
-		| CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnList ')' opt_reloptions
+		| CREATE OR REPLACE OptTemp RECURSIVE VIEW qualified_name '(' columnListOptionalComma ')' opt_reloptions
 				AS SelectStmt opt_check_option
 				{
 					ViewStmt   *n = makeNode(ViewStmt);
@@ -11754,14 +11894,12 @@ DropdbStmt: DROP DATABASE name
 		;
 
 drop_option_list:
-			drop_option
-				{
-					$$ = list_make1((Node *) $1);
-				}
-			| drop_option_list ',' drop_option
-				{
-					$$ = lappend($1, (Node *) $3);
-				}
+			drop_option_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+drop_option_list_items:
+			drop_option									{ $$ = list_make1($1); }
+			| drop_option_list_items ',' drop_option	{ $$ = lappend($1, $3); }
 		;
 
 /*
@@ -12197,10 +12335,12 @@ vacuum_relation:
 		;
 
 vacuum_relation_list:
-			vacuum_relation
-					{ $$ = list_make1($1); }
-			| vacuum_relation_list ',' vacuum_relation
-					{ $$ = lappend($1, $3); }
+			vacuum_relation_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+vacuum_relation_list_items:
+			vacuum_relation										{ $$ = list_make1($1); }
+			| vacuum_relation_list_items ',' vacuum_relation	{ $$ = lappend($1, $3); }
 		;
 
 opt_vacuum_relation_list:
@@ -12478,10 +12618,12 @@ override_kind:
 		;
 
 insert_column_list:
-			insert_column_item
-					{ $$ = list_make1($1); }
-			| insert_column_list ',' insert_column_item
-					{ $$ = lappend($1, $3); }
+			insert_column_list_items opt_trailing_comma			{ $$ = $1; }
+		;
+
+insert_column_list_items:
+			insert_column_item									{ $$ = list_make1($1); }
+			| insert_column_list_items ',' insert_column_item	{ $$ = lappend($1, $3); }
 		;
 
 insert_column_item:
@@ -12684,8 +12826,12 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 		;
 
 set_clause_list:
-			set_clause							{ $$ = $1; }
-			| set_clause_list ',' set_clause	{ $$ = list_concat($1,$3); }
+			set_clause_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+set_clause_list_items:
+			set_clause									{ $$ = $1; }
+			| set_clause_list_items ',' set_clause		{ $$ = list_concat($1,$3); }
 		;
 
 set_clause:
@@ -12729,10 +12875,13 @@ set_target:
 		;
 
 set_target_list:
-			set_target								{ $$ = list_make1($1); }
-			| set_target_list ',' set_target		{ $$ = lappend($1,$3); }
+			set_target_list_items opt_trailing_comma		{ $$ = $1; }
 		;
 
+set_target_list_items:
+			set_target										{ $$ = list_make1($1); }
+			| set_target_list_items ',' set_target			{ $$ = lappend($1,$3); }
+		;
 
 /*****************************************************************************
  *
@@ -13232,23 +13381,23 @@ opt_materialized:
 		;
 
 opt_search_clause:
-		SEARCH DEPTH FIRST_P BY columnList SET ColId
+		SEARCH depth_or_breadth FIRST_P BY columnList SET ColId
 			{
 				CTESearchClause *n = makeNode(CTESearchClause);
 
 				n->search_col_list = $5;
-				n->search_breadth_first = false;
+				n->search_breadth_first = $2;
 				n->search_seq_column = $7;
 				n->location = @1;
 				$$ = (Node *) n;
 			}
-		| SEARCH BREADTH FIRST_P BY columnList SET ColId
+		| SEARCH depth_or_breadth FIRST_P BY columnList ',' SET ColId
 			{
 				CTESearchClause *n = makeNode(CTESearchClause);
 
 				n->search_col_list = $5;
-				n->search_breadth_first = true;
-				n->search_seq_column = $7;
+				n->search_breadth_first = $2;
+				n->search_seq_column = $8;
 				n->location = @1;
 				$$ = (Node *) n;
 			}
@@ -13258,6 +13407,11 @@ opt_search_clause:
 			}
 		;
 
+depth_or_breadth:
+		DEPTH		{ $$ = false; }
+		| BREADTH	{ $$ = true; }
+	;
+
 opt_cycle_clause:
 		CYCLE columnList SET ColId TO AexprConst DEFAULT AexprConst USING ColId
 			{
@@ -13271,6 +13425,18 @@ opt_cycle_clause:
 				n->location = @1;
 				$$ = (Node *) n;
 			}
+		| CYCLE columnList ',' SET ColId TO AexprConst DEFAULT AexprConst USING ColId
+			{
+				CTECycleClause *n = makeNode(CTECycleClause);
+
+				n->cycle_col_list = $2;
+				n->cycle_mark_column = $5;
+				n->cycle_mark_value = $7;
+				n->cycle_mark_default = $9;
+				n->cycle_path_column = $11;
+				n->location = @1;
+				$$ = (Node *) n;
+			}
 		| CYCLE columnList SET ColId USING ColId
 			{
 				CTECycleClause *n = makeNode(CTECycleClause);
@@ -13283,6 +13449,18 @@ opt_cycle_clause:
 				n->location = @1;
 				$$ = (Node *) n;
 			}
+		| CYCLE columnList ',' SET ColId USING ColId
+			{
+				CTECycleClause *n = makeNode(CTECycleClause);
+
+				n->cycle_col_list = $2;
+				n->cycle_mark_column = $5;
+				n->cycle_mark_value = makeBoolAConst(true, -1);
+				n->cycle_mark_default = makeBoolAConst(false, -1);
+				n->cycle_path_column = $7;
+				n->location = @1;
+				$$ = (Node *) n;
+			}
 		| /*EMPTY*/
 			{
 				$$ = NULL;
@@ -13405,9 +13583,18 @@ sort_clause:
 			ORDER BY sortby_list					{ $$ = $3; }
 		;
 
+sortby_list_no_trailing_comma:
+			sortby										{ $$ = list_make1($1); }
+			| sortby_list_no_trailing_comma ',' sortby	{ $$ = lappend($1, $3); }
+		;
+
 sortby_list:
-			sortby									{ $$ = list_make1($1); }
-			| sortby_list ',' sortby				{ $$ = lappend($1, $3); }
+			sortby_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+sortby_list_items:
+			sortby										{ $$ = list_make1($1); }
+			| sortby_list_items ',' sortby				{ $$ = lappend($1, $3); }
 		;
 
 sortby:		a_expr USING qual_all_Op opt_nulls_order
@@ -13656,8 +13843,12 @@ group_clause:
 		;
 
 group_by_list:
-			group_by_item							{ $$ = list_make1($1); }
-			| group_by_list ',' group_by_item		{ $$ = lappend($1,$3); }
+			group_by_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+group_by_list_items:
+			group_by_item								{ $$ = list_make1($1); }
+			| group_by_list_items ',' group_by_item		{ $$ = lappend($1,$3); }
 		;
 
 group_by_item:
@@ -13784,8 +13975,12 @@ from_clause:
 		;
 
 from_list:
+			from_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+from_list_items:
 			table_ref								{ $$ = list_make1($1); }
-			| from_list ',' table_ref				{ $$ = lappend($1, $3); }
+			| from_list_items ',' table_ref			{ $$ = lappend($1, $3); }
 		;
 
 /*
@@ -14142,10 +14337,13 @@ extended_relation_expr:
 
 
 relation_expr_list:
-			relation_expr							{ $$ = list_make1($1); }
-			| relation_expr_list ',' relation_expr	{ $$ = lappend($1, $3); }
+			relation_expr_list_items opt_trailing_comma		{ $$ = $1; }
 		;
 
+relation_expr_list_items:
+			relation_expr									{ $$ = list_make1($1); }
+			| relation_expr_list_items ',' relation_expr	{ $$ = lappend($1, $3); }
+		;
 
 /*
  * Given "UPDATE foo set set ...", we have to decide without looking any
@@ -14241,8 +14439,12 @@ rowsfrom_item: func_expr_windowless opt_col_def_list
 		;
 
 rowsfrom_list:
-			rowsfrom_item						{ $$ = list_make1($1); }
-			| rowsfrom_list ',' rowsfrom_item	{ $$ = lappend($1, $3); }
+			rowsfrom_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+rowsfrom_list_items:
+			rowsfrom_item							{ $$ = list_make1($1); }
+			| rowsfrom_list_items ',' rowsfrom_item	{ $$ = lappend($1, $3); }
 		;
 
 opt_col_def_list: AS '(' TableFuncElementList ')'	{ $$ = $3; }
@@ -14281,14 +14483,12 @@ OptTableFuncElementList:
 		;
 
 TableFuncElementList:
-			TableFuncElement
-				{
-					$$ = list_make1($1);
-				}
-			| TableFuncElementList ',' TableFuncElement
-				{
-					$$ = lappend($1, $3);
-				}
+			TableFuncElementListItems opt_trailing_comma		{ $$ = $1; }
+		;
+
+TableFuncElementListItems:
+			TableFuncElement									{ $$ = list_make1($1); }
+			| TableFuncElementListItems ',' TableFuncElement	{ $$ = lappend($1, $3);	}
 		;
 
 TableFuncElement:	ColId Typename opt_collate_clause
@@ -14341,8 +14541,13 @@ xmltable:
 				}
 		;
 
-xmltable_column_list: xmltable_column_el					{ $$ = list_make1($1); }
-			| xmltable_column_list ',' xmltable_column_el	{ $$ = lappend($1, $3); }
+xmltable_column_list:
+			xmltable_column_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+xmltable_column_list_items:
+			xmltable_column_el									{ $$ = list_make1($1); }
+			| xmltable_column_list_items ',' xmltable_column_el	{ $$ = lappend($1, $3); }
 		;
 
 xmltable_column_el:
@@ -14458,10 +14663,12 @@ xmltable_column_option_el:
 		;
 
 xml_namespace_list:
-			xml_namespace_el
-				{ $$ = list_make1($1); }
-			| xml_namespace_list ',' xml_namespace_el
-				{ $$ = lappend($1, $3); }
+			xml_namespace_list_items opt_trailing_comma			{ $$ = $1; }
+		;
+
+xml_namespace_list_items:
+			xml_namespace_el									{ $$ = list_make1($1); }
+			| xml_namespace_list_items ',' xml_namespace_el		{ $$ = lappend($1, $3); }
 		;
 
 xml_namespace_el:
@@ -14517,10 +14724,13 @@ json_table_path_name_opt:
 		;
 
 json_table_column_definition_list:
-			json_table_column_definition
-				{ $$ = list_make1($1); }
-			| json_table_column_definition_list ',' json_table_column_definition
-				{ $$ = lappend($1, $3); }
+			json_table_column_definition_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+json_table_column_definition_list_items:
+			json_table_column_definition								{ $$ = list_make1($1); }
+			| json_table_column_definition_list_items ',' json_table_column_definition
+																		{ $$ = lappend($1, $3); }
 		;
 
 json_table_column_definition:
@@ -15844,7 +16054,7 @@ func_application: func_name '(' ')'
 											   COERCE_EXPLICIT_CALL,
 											   @1);
 				}
-			| func_name '(' func_arg_list opt_sort_clause ')'
+			| func_name '(' func_arg_list_no_trailing_comma opt_sort_clause ')'
 				{
 					FuncCall   *n = makeFuncCall($1, $3,
 												 COERCE_EXPLICIT_CALL,
@@ -15853,6 +16063,15 @@ func_application: func_name '(' ')'
 					n->agg_order = $4;
 					$$ = (Node *) n;
 				}
+			| func_name '(' func_arg_list_no_trailing_comma ',' opt_sort_clause ')'
+				{
+					FuncCall   *n = makeFuncCall($1, $3,
+												 COERCE_EXPLICIT_CALL,
+												 @1);
+
+					n->agg_order = $5;
+					$$ = (Node *) n;
+				}
 			| func_name '(' VARIADIC func_arg_expr opt_sort_clause ')'
 				{
 					FuncCall   *n = makeFuncCall($1, list_make1($4),
@@ -15863,7 +16082,7 @@ func_application: func_name '(' ')'
 					n->agg_order = $5;
 					$$ = (Node *) n;
 				}
-			| func_name '(' func_arg_list ',' VARIADIC func_arg_expr opt_sort_clause ')'
+			| func_name '(' func_arg_list_no_trailing_comma ',' VARIADIC func_arg_expr opt_sort_clause ')'
 				{
 					FuncCall   *n = makeFuncCall($1, lappend($3, $6),
 												 COERCE_EXPLICIT_CALL,
@@ -16478,8 +16697,13 @@ opt_xml_root_standalone: ',' STANDALONE_P YES_P
 xml_attributes: XMLATTRIBUTES '(' xml_attribute_list ')'	{ $$ = $3; }
 		;
 
-xml_attribute_list:	xml_attribute_el					{ $$ = list_make1($1); }
-			| xml_attribute_list ',' xml_attribute_el	{ $$ = lappend($1, $3); }
+xml_attribute_list:
+			xml_attribute_list_items opt_trailing_comma			{ $$ = $1; }
+		;
+
+xml_attribute_list_items:
+			xml_attribute_el									{ $$ = list_make1($1); }
+			| xml_attribute_list_items ',' xml_attribute_el		{ $$ = lappend($1, $3); }
 		;
 
 xml_attribute_el: a_expr AS ColLabel
@@ -16589,9 +16813,13 @@ window_clause:
 		;
 
 window_definition_list:
-			window_definition						{ $$ = list_make1($1); }
-			| window_definition_list ',' window_definition
-													{ $$ = lappend($1, $3); }
+			window_definition_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+window_definition_list_items:
+			window_definition								{ $$ = list_make1($1); }
+			| window_definition_list_items ',' window_definition
+															{ $$ = lappend($1, $3); }
 		;
 
 window_definition:
@@ -16830,16 +17058,17 @@ opt_window_exclusion_clause:
  * without conflicting with the parenthesized a_expr production.  Without the
  * ROW keyword, there must be more than one a_expr inside the parens.
  */
-row:		ROW '(' expr_list ')'					{ $$ = $3; }
-			| ROW '(' ')'							{ $$ = NIL; }
-			| '(' expr_list ',' a_expr ')'			{ $$ = lappend($2, $4); }
+row:		ROW '(' expr_list_no_trailing_comma ')'			 	{ $$ = $3; }
+			| ROW '(' ')'										{ $$ = NIL; }
+			| '(' expr_list_no_trailing_comma ',' a_expr ')'  	{ $$ = lappend($2, $4); }
 		;
 
-explicit_row:	ROW '(' expr_list ')'				{ $$ = $3; }
-			| ROW '(' ')'							{ $$ = NIL; }
+explicit_row:	ROW '(' expr_list_no_trailing_comma ')'	   		{ $$ = $3; }
+			| ROW '(' expr_list_no_trailing_comma ',' ')'		{ $$ = $3; }
+			| ROW '(' ')'							   			{ $$ = NIL; }
 		;
 
-implicit_row:	'(' expr_list ',' a_expr ')'		{ $$ = lappend($2, $4); }
+implicit_row:	'(' expr_list_no_trailing_comma ',' a_expr ')'	{ $$ = lappend($2, $4); }
 		;
 
 sub_type:	ANY										{ $$ = ANY_SUBLINK; }
@@ -16901,25 +17130,34 @@ subquery_Op:
  */
 			;
 
-expr_list:	a_expr
-				{
-					$$ = list_make1($1);
-				}
-			| expr_list ',' a_expr
-				{
-					$$ = lappend($1, $3);
-				}
+expr_list_no_trailing_comma:
+			a_expr								{ $$ = list_make1($1); }
+			| expr_list_no_trailing_comma ',' a_expr		{ $$ = lappend($1, $3); }
+		;
+
+expr_list:
+			expr_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+expr_list_items:
+			a_expr								{ $$ = list_make1($1); }
+			| expr_list_items ',' a_expr		{ $$ = lappend($1, $3); }
 		;
 
 /* function arguments can have names */
-func_arg_list:  func_arg_expr
-				{
-					$$ = list_make1($1);
-				}
-			| func_arg_list ',' func_arg_expr
-				{
-					$$ = lappend($1, $3);
-				}
+func_arg_list_no_trailing_comma:
+			func_arg_expr								{ $$ = list_make1($1); }
+			| func_arg_list_no_trailing_comma ',' func_arg_expr
+														{ $$ = lappend($1, $3); }
+		;
+
+func_arg_list:
+			func_arg_list_items opt_trailing_comma		{ $$ = $1; }
+		;
+
+func_arg_list_items:
+			func_arg_expr								{ $$ = list_make1($1); }
+			| func_arg_list_items ',' func_arg_expr		{ $$ = lappend($1, $3); }
 		;
 
 func_arg_expr:  a_expr
@@ -16952,8 +17190,12 @@ func_arg_list_opt:	func_arg_list					{ $$ = $1; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
-type_list:	Typename								{ $$ = list_make1($1); }
-			| type_list ',' Typename				{ $$ = lappend($1, $3); }
+type_list:
+			type_list_items opt_trailing_comma		{ $$ = $1; }
+
+type_list_items:
+			Typename								{ $$ = list_make1($1); }
+			| type_list_items ',' Typename			{ $$ = lappend($1, $3); }
 		;
 
 array_expr: '[' expr_list ']'
@@ -16970,10 +17212,14 @@ array_expr: '[' expr_list ']'
 				}
 		;
 
-array_expr_list: array_expr							{ $$ = list_make1($1); }
-			| array_expr_list ',' array_expr		{ $$ = lappend($1, $3); }
+array_expr_list:
+			array_expr_list_items opt_trailing_comma		{ $$ = $1; }
 		;
 
+array_expr_list_items:
+			array_expr										{ $$ = list_make1($1); }
+			| array_expr_list_items ',' array_expr			{ $$ = lappend($1, $3); }
+		;
 
 extract_list:
 			extract_arg FROM a_expr
@@ -17439,7 +17685,7 @@ json_aggregate_func:
 		;
 
 json_array_aggregate_order_by_clause_opt:
-			ORDER BY sortby_list					{ $$ = $3; }
+			ORDER BY sortby_list_no_trailing_comma	{ $$ = $3; }
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
@@ -17454,9 +17700,13 @@ opt_target_list: target_list						{ $$ = $1; }
 		;
 
 target_list:
+			target_list_items  opt_trailing_comma	{ $$ = $1; }
+			;
+
+target_list_items:
 			target_el								{ $$ = list_make1($1); }
-			| target_list ',' target_el				{ $$ = lappend($1, $3); }
-		;
+			| target_list_items ',' target_el		{ $$ = lappend($1, $3); }
+			;
 
 target_el:	a_expr AS ColLabel
 				{
@@ -17505,8 +17755,12 @@ target_el:	a_expr AS ColLabel
  *****************************************************************************/
 
 qualified_name_list:
-			qualified_name							{ $$ = list_make1($1); }
-			| qualified_name_list ',' qualified_name { $$ = lappend($1, $3); }
+			qualified_name_list_items opt_trailing_comma	{ $$ = $1; }
+		;
+
+qualified_name_list_items:
+			qualified_name									{ $$ = list_make1($1); }
+			| qualified_name_list_items ',' qualified_name	{ $$ = lappend($1, $3); }
 		;
 
 /*
@@ -17527,12 +17781,14 @@ qualified_name:
 				}
 		;
 
-name_list:	name
-					{ $$ = list_make1(makeString($1)); }
-			| name_list ',' name
-					{ $$ = lappend($1, makeString($3)); }
+name_list:
+			name_list_items opt_trailing_comma		{ $$ = $1; }
 		;
 
+name_list_items:
+			name									{ $$ = list_make1(makeString($1)); }
+			| name_list_items ',' name				{ $$ = lappend($1, makeString($3)); }
+		;
 
 name:		ColId									{ $$ = $1; };
 
@@ -17594,7 +17850,7 @@ AexprConst: Iconst
 					t->location = @1;
 					$$ = makeStringConstCast($2, @2, t);
 				}
-			| func_name '(' func_arg_list opt_sort_clause ')' Sconst
+			| func_name '(' func_arg_list_no_trailing_comma opt_sort_clause ')' Sconst
 				{
 					/* generic syntax with a type modifier */
 					TypeName   *t = makeTypeNameFromNameList($1);
@@ -17751,12 +18007,14 @@ RoleSpec:	NonReservedWord
 				}
 		;
 
-role_list:	RoleSpec
-				{ $$ = list_make1($1); }
-			| role_list ',' RoleSpec
-				{ $$ = lappend($1, $3); }
+role_list:
+			role_list_items opt_trailing_comma	{ $$ = $1; }
 		;
 
+role_list_items:
+			RoleSpec							{ $$ = list_make1($1); }
+			| role_list_items ',' RoleSpec		{ $$ = lappend($1, $3); }
+		;
 
 /*****************************************************************************
  *
-- 
2.47.3

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Sabino Mullane (#1)
Re: [PATCH] Provide support for trailing commas

Greg Sabino Mullane <htamfids@gmail.com> writes:

Happy New Year! Please find attached a patch to provide comprehensive
trailing comma support, where we allow (when possible) a comma that does
NOT come before the next item in a list. To wit:

SELECT 1,2,3, FROM pg_class, ORDER BY greatest(relpages,1,);

Did we not discuss and reject this very idea in the past?
A quick search finds [1]/messages/by-id/20140928114246.GA22150@artax.karlin.mff.cuni.cz[2]/messages/by-id/0368c60f-abe2-4f5f-972d-7cd1e6db2382@gmail.com, and there may be more threads.

This is not as trivial a task as it seems, as those who have dabbled with
our parser (or parsers in general) may suspect.

Indeed. I think it carries substantial risk of creating large parsing
problems down the road, when we try to integrate whatever random
syntax the SQL committee comes up with next. And frankly, I don't
agree that it'd be beneficial.

regards, tom lane

[1]: /messages/by-id/20140928114246.GA22150@artax.karlin.mff.cuni.cz
[2]: /messages/by-id/0368c60f-abe2-4f5f-972d-7cd1e6db2382@gmail.com

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#2)
Re: [PATCH] Provide support for trailing commas

út 6. 1. 2026 v 21:49 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Greg Sabino Mullane <htamfids@gmail.com> writes:

Happy New Year! Please find attached a patch to provide comprehensive
trailing comma support, where we allow (when possible) a comma that does
NOT come before the next item in a list. To wit:

SELECT 1,2,3, FROM pg_class, ORDER BY greatest(relpages,1,);

Did we not discuss and reject this very idea in the past?
A quick search finds [1][2], and there may be more threads.

This is not as trivial a task as it seems, as those who have dabbled with
our parser (or parsers in general) may suspect.

Indeed. I think it carries substantial risk of creating large parsing
problems down the road, when we try to integrate whatever random
syntax the SQL committee comes up with next. And frankly, I don't
agree that it'd be beneficial.

+1

Pavel

Show quoted text

regards, tom lane

[1]
/messages/by-id/20140928114246.GA22150@artax.karlin.mff.cuni.cz
[2]
/messages/by-id/0368c60f-abe2-4f5f-972d-7cd1e6db2382@gmail.com

#4Greg Sabino Mullane
htamfids@gmail.com
In reply to: Tom Lane (#2)
Re: [PATCH] Provide support for trailing commas

On Tue, Jan 6, 2026 at 3:48 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Indeed. I think it carries substantial risk of creating large parsing
problems down the road, when we try to integrate whatever random syntax the
SQL committee comes up with next. And frankly, I don't agree that it'd be
beneficial.

Hmmm, I was partly inspired to write this by your comment in the second
link you referenced, in which you said:

Yeah, a more principled approach would be to not special-case target
lists, but to allow one (and only one) trailing comma everywhere:
select, order by, group by, array constructors, row constructors,
everything that looks like a function call, etc.

If it can be made to work everywhere, that would get my vote.

So, this is an attempt to be more comprehensive, which has been one of the
major critiques of previous attempts to apply things to a small subset of
the things we parse.

No, it's not yet in the SQL standard, but why not be more liberal in what
we accept, and not have people invent various workaround hacks (e.g. dummy
columns)? Because the parser eats the comma, there are no behavioral
changes post-parsing.

Unsurprisingly, I also don't agree that new SQL committee changes will be
that hard to support around some trailing comma support. The syntax moves
slowly, and very few things will impact our comma-separated lists. The most
likely things is additional modifiers e.g. RESTART IDENTITY but once we are
handling one, we already have a framework in place to handle future ones.

Cheers,
Greg

#5David Rowley
dgrowleyml@gmail.com
In reply to: Greg Sabino Mullane (#4)
Re: [PATCH] Provide support for trailing commas

On Thu, 8 Jan 2026 at 03:07, Greg Sabino Mullane <htamfids@gmail.com> wrote:

No, it's not yet in the SQL standard, but why not be more liberal in what we accept,

I believe the concern is that it could hinder us from being able to
implement new SQL standard features. Back in 8.4, "AS" became optional
in the SELECT list. I recall that it was a bit tricky to get around
some grammar ambiguities with that. There's some detail on that in the
comment added in e67867b26.

and not have people invent various workaround hacks (e.g. dummy columns)? Because the parser eats the comma, there are no behavioral changes post-parsing.

Why do people have to add dummy columns? Is this because these people
don't know how to program a check to see if they're on the first
iteration of a loop or not?

Unsurprisingly, I also don't agree that new SQL committee changes will be that hard to support around some trailing comma support.

To me, that seems like a risky thing to disagree with. I expect you
don't have a vision of what the standard committee might dream up
infinitely far into the future.

Personally, I don't want this feature. I'd rather receive an error
message when I make silly mistakes in code that I write. If I have
code to dynamically build an SQL statement and I have a bug that
causes a variable that stores a column name to sometimes be an empty
string, with your patch, I'll get an error message in all cases unless
the column's position is last. I want the error at all times, not
sometimes.

David

#6Bruce Momjian
bruce@momjian.us
In reply to: Greg Sabino Mullane (#1)
Re: [PATCH] Provide support for trailing commas

On Tue, Jan 6, 2026 at 03:35:38PM -0500, Greg Sabino Mullane wrote:

tl;dr Provide support for trailing commas, where possible and practical.

(Disclaimer: No LLM or AI was used to craft this patch or this email)

Happy New Year! Please find attached a patch to provide comprehensive trailing
comma support, where we allow (when possible) a comma that does NOT come before
the next item in a list. To wit:

I am unclear why you bothered to create and work on such a large email
and patch rather than ask if this was a desired change; see:

https://wiki.postgresql.org/wiki/Todo#Development_Process

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#7Andres Freund
andres@anarazel.de
In reply to: Bruce Momjian (#6)
Re: [PATCH] Provide support for trailing commas

Hi,

On January 7, 2026 11:44:29 PM EST, Bruce Momjian <bruce@momjian.us> wrote:

On Tue, Jan 6, 2026 at 03:35:38PM -0500, Greg Sabino Mullane wrote:

tl;dr Provide support for trailing commas, where possible and practical.

(Disclaimer: No LLM or AI was used to craft this patch or this email)

Happy New Year! Please find attached a patch to provide comprehensive trailing
comma support, where we allow (when possible) a comma that does NOT come before
the next item in a list. To wit:

I am unclear why you bothered to create and work on such a large email
and patch rather than ask if this was a desired change; see:

https://wiki.postgresql.org/wiki/Todo#Development_Process

Meh. This kind of change really needs information about the implementation cost to be judged. The process described there rarely is a good idea for anybody that has some community experience.

Greetings,

Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.

#8Bruce Momjian
bruce@momjian.us
In reply to: Andres Freund (#7)
Re: [PATCH] Provide support for trailing commas

On Thu, Jan 8, 2026 at 12:51:14AM -0500, Andres Freund wrote:

Hi,

On January 7, 2026 11:44:29 PM EST, Bruce Momjian <bruce@momjian.us> wrote:

On Tue, Jan 6, 2026 at 03:35:38PM -0500, Greg Sabino Mullane wrote:

tl;dr Provide support for trailing commas, where possible and practical.

(Disclaimer: No LLM or AI was used to craft this patch or this email)

Happy New Year! Please find attached a patch to provide comprehensive trailing
comma support, where we allow (when possible) a comma that does NOT come before
the next item in a list. To wit:

I am unclear why you bothered to create and work on such a large email
and patch rather than ask if this was a desired change; see:

https://wiki.postgresql.org/wiki/Todo#Development_Process

Meh. This kind of change really needs information about the
implementation cost to be judged. The process described there rarely is
a good idea for anybody that has some community experience.

I disagree. It was clear in this case that it was the concept that was
not wanted, rather than the implementation cost.

--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EDB https://enterprisedb.com

Do not let urgent matters crowd out time for investment in the future.

#9Andres Freund
andres@anarazel.de
In reply to: Bruce Momjian (#8)
Re: [PATCH] Provide support for trailing commas

Hi,

On 2026-01-08 10:03:22 -0500, Bruce Momjian wrote:

On Thu, Jan 8, 2026 at 12:51:14AM -0500, Andres Freund wrote:

Hi,

On January 7, 2026 11:44:29 PM EST, Bruce Momjian <bruce@momjian.us> wrote:

On Tue, Jan 6, 2026 at 03:35:38PM -0500, Greg Sabino Mullane wrote:

tl;dr Provide support for trailing commas, where possible and practical.

(Disclaimer: No LLM or AI was used to craft this patch or this email)

Happy New Year! Please find attached a patch to provide comprehensive trailing
comma support, where we allow (when possible) a comma that does NOT come before
the next item in a list. To wit:

I am unclear why you bothered to create and work on such a large email
and patch rather than ask if this was a desired change; see:

https://wiki.postgresql.org/wiki/Todo#Development_Process

Meh. This kind of change really needs information about the
implementation cost to be judged. The process described there rarely is
a good idea for anybody that has some community experience.

I disagree. It was clear in this case that it was the concept that was
not wanted, rather than the implementation cost.

I don't agree with that, fwiw. I it were easy to implement, I think we ought
to allow trailing commas in many places, most importantly the select list and
from list.

Greetings,

Andres Freund

#10Jelte Fennema-Nio
postgres@jeltef.nl
In reply to: Bruce Momjian (#8)
Re: [PATCH] Provide support for trailing commas

On Thu, 8 Jan 2026 at 16:03, Bruce Momjian <bruce@momjian.us> wrote:

I disagree. It was clear in this case that it was the concept that was
not wanted, rather than the implementation cost.

It definitely wasn't clear IMO. See the quote from Tom, that Greg
shared from that thread. Tom at that point said he'd be in favor, IF
it would be done everywhere. Which is exactly what Greg did.

Ofcourse Tom is allowed to change his opinion, but if I'd felt
strongly enough about this then I'd also have implemented this first
based on the previous thread.

For the record, I'd very much like trailing commas to work. Just not
enough to put the time in to discuss them at length. But I'd
definitely like to not get errors thrown in my face every time I do a
trivial modification of a query, and I'd also like my git diffs not to
touch lines that logically have nothing to do with the change I'm
actually doing.

#11Bernd Helmle
mailings@oopsware.de
In reply to: David Rowley (#5)
Re: [PATCH] Provide support for trailing commas

Am Donnerstag, dem 08.01.2026 um 10:53 +1300 schrieb David Rowley:

Personally, I don't want this feature. I'd rather receive an error
message when I make silly mistakes in code that I write. If I have
code to dynamically build an SQL statement and I have a bug that
causes a variable that stores a column name to sometimes be an empty
string, with your patch, I'll get an error message in all cases
unless
the column's position is last. I want the error at all times, not
sometimes.

This is exactly what i initially thought when reading through this
thread. Yes, people have their arguments, but it also feels like a big
hammer for a rather small issue, looking on what technical implications
are discussed and might hit us.

In other words, I never got a single complaint from people using
PostgreSQL in the last two decades for not allowing trailing commas in
our parser. And to be honest, i never thought about this before Greg's
proposal ...

--
Thanks,
Bernd