From 579f0fa8b8834f454abc6e91a901938fccdb71d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:25:39 +0000 Subject: [PATCH 1/8] Initial plan From ffd6cc8baaf598332aa96bbc09d08e52417edffc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:30:48 +0000 Subject: [PATCH 2/8] Fix CREATE INDEX CONCURRENTLY IF NOT EXISTS invalidating existing index Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- src/backend/distributed/commands/index.c | 29 ++++++++++++++++++- .../regress/sql/multi_index_statements.sql | 9 ++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index 0538f51b3b7..076851b6d6b 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -19,6 +19,7 @@ #include "catalog/index.h" #include "catalog/namespace.h" #include "catalog/pg_class.h" +#include "catalog/pg_index.h" #include "catalog/pg_namespace.h" #include "commands/defrem.h" #include "commands/tablecmds.h" @@ -853,8 +854,34 @@ PostprocessIndexStmt(Node *node, const char *queryString) PushActiveSnapshot(GetTransactionSnapshot()); + /* + * If IF NOT EXISTS was used and the index already existed (is valid), + * skip invalidating it. This prevents invalidating an existing index + * when CREATE INDEX CONCURRENTLY IF NOT EXISTS is run on an already + * existing index. + */ + bool shouldInvalidateIndex = true; + if (indexStmt->if_not_exists) + { + HeapTuple indexTuple = SearchSysCache1(INDEXRELID, + ObjectIdGetDatum(indexRelationId)); + if (HeapTupleIsValid(indexTuple)) + { + Form_pg_index indexForm = (Form_pg_index) GETSTRUCT(indexTuple); + if (indexForm->indisvalid) + { + /* Index was already valid, so it existed before this command */ + shouldInvalidateIndex = false; + } + ReleaseSysCache(indexTuple); + } + } + /* mark index as invalid, in-place (cannot be rolled back) */ - index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); + if (shouldInvalidateIndex) + { + index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); + } PopActiveSnapshot(); /* re-open a transaction command from here on out */ diff --git a/src/test/regress/sql/multi_index_statements.sql b/src/test/regress/sql/multi_index_statements.sql index e6e7dd60744..152e926b774 100644 --- a/src/test/regress/sql/multi_index_statements.sql +++ b/src/test/regress/sql/multi_index_statements.sql @@ -104,6 +104,15 @@ CREATE INDEX IF NOT EXISTS lineitem_orderkey_index_new on public.lineitem(l_orde CREATE INDEX lineitem_orderkey_index on public.nation(n_nationkey); CREATE INDEX IF NOT EXISTS lineitem_orderkey_index on public.nation(n_nationkey); +-- Verify that CREATE INDEX CONCURRENTLY IF NOT EXISTS doesn't invalidate existing index +CREATE INDEX CONCURRENTLY lineitem_concurrent_if_not_exists_test ON public.lineitem (l_partkey); +-- Verify the index is valid +SELECT indisvalid FROM pg_index WHERE indexrelid = 'public.lineitem_concurrent_if_not_exists_test'::regclass; +-- Run the same command again with IF NOT EXISTS +CREATE INDEX CONCURRENTLY IF NOT EXISTS lineitem_concurrent_if_not_exists_test ON public.lineitem (l_partkey); +-- Verify the index is still valid (not invalidated) +SELECT indisvalid FROM pg_index WHERE indexrelid = 'public.lineitem_concurrent_if_not_exists_test'::regclass; + -- Verify that we can create indexes concurrently CREATE INDEX CONCURRENTLY lineitem_concurrently_index ON public.lineitem (l_orderkey); From 006a902d8ff29b27c525dd7bf90597fafd1bcbbd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:31:19 +0000 Subject: [PATCH 3/8] Add expected output for CREATE INDEX CONCURRENTLY IF NOT EXISTS test Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- .../expected/multi_index_statements.out | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/regress/expected/multi_index_statements.out b/src/test/regress/expected/multi_index_statements.out index 0a08ede3784..642278e3993 100644 --- a/src/test/regress/expected/multi_index_statements.out +++ b/src/test/regress/expected/multi_index_statements.out @@ -136,6 +136,25 @@ CREATE INDEX lineitem_orderkey_index on public.nation(n_nationkey); ERROR: relation "lineitem_orderkey_index" already exists CREATE INDEX IF NOT EXISTS lineitem_orderkey_index on public.nation(n_nationkey); NOTICE: relation "lineitem_orderkey_index" already exists, skipping +-- Verify that CREATE INDEX CONCURRENTLY IF NOT EXISTS doesn't invalidate existing index +CREATE INDEX CONCURRENTLY lineitem_concurrent_if_not_exists_test ON public.lineitem (l_partkey); +-- Verify the index is valid +SELECT indisvalid FROM pg_index WHERE indexrelid = 'public.lineitem_concurrent_if_not_exists_test'::regclass; + indisvalid +--------------------------------------------------------------------- + t +(1 row) + +-- Run the same command again with IF NOT EXISTS +CREATE INDEX CONCURRENTLY IF NOT EXISTS lineitem_concurrent_if_not_exists_test ON public.lineitem (l_partkey); +NOTICE: relation "lineitem_concurrent_if_not_exists_test" already exists, skipping +-- Verify the index is still valid (not invalidated) +SELECT indisvalid FROM pg_index WHERE indexrelid = 'public.lineitem_concurrent_if_not_exists_test'::regclass; + indisvalid +--------------------------------------------------------------------- + t +(1 row) + -- Verify that we can create indexes concurrently CREATE INDEX CONCURRENTLY lineitem_concurrently_index ON public.lineitem (l_orderkey); -- Verify that no-name local CREATE INDEX CONCURRENTLY works From 6103c5113e9d70d2709e4efd9a8211c6d6cbc881 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:31:54 +0000 Subject: [PATCH 4/8] Fix variable naming to follow snake_case convention Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- src/backend/distributed/commands/index.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index 076851b6d6b..525cad54a34 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -860,7 +860,7 @@ PostprocessIndexStmt(Node *node, const char *queryString) * when CREATE INDEX CONCURRENTLY IF NOT EXISTS is run on an already * existing index. */ - bool shouldInvalidateIndex = true; + bool should_invalidate_index = true; if (indexStmt->if_not_exists) { HeapTuple indexTuple = SearchSysCache1(INDEXRELID, @@ -871,14 +871,14 @@ PostprocessIndexStmt(Node *node, const char *queryString) if (indexForm->indisvalid) { /* Index was already valid, so it existed before this command */ - shouldInvalidateIndex = false; + should_invalidate_index = false; } ReleaseSysCache(indexTuple); } } /* mark index as invalid, in-place (cannot be rolled back) */ - if (shouldInvalidateIndex) + if (should_invalidate_index) { index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); } From f9364646282840ca5364c18ea9697c7cd1aac617 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:32:30 +0000 Subject: [PATCH 5/8] Improve code clarity based on review feedback Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- src/backend/distributed/commands/index.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index 525cad54a34..96d8a69e0a6 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -860,9 +860,14 @@ PostprocessIndexStmt(Node *node, const char *queryString) * when CREATE INDEX CONCURRENTLY IF NOT EXISTS is run on an already * existing index. */ - bool should_invalidate_index = true; + bool skip_invalidation = false; if (indexStmt->if_not_exists) { + /* + * Check the current validity state of the index. If it's already valid, + * it means the index existed before this command and PostgreSQL skipped + * creation. We should not invalidate it in this case. + */ HeapTuple indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexRelationId)); if (HeapTupleIsValid(indexTuple)) @@ -871,14 +876,14 @@ PostprocessIndexStmt(Node *node, const char *queryString) if (indexForm->indisvalid) { /* Index was already valid, so it existed before this command */ - should_invalidate_index = false; + skip_invalidation = true; } ReleaseSysCache(indexTuple); } } /* mark index as invalid, in-place (cannot be rolled back) */ - if (should_invalidate_index) + if (!skip_invalidation) { index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); } From a18f08376f98e501dacb4df494636a22c1fe00f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:33:40 +0000 Subject: [PATCH 6/8] Final improvements: better variable naming and additional comments Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- _codeql_detected_source_root | 1 + src/backend/distributed/commands/index.c | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000000..945c9b46d68 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index 96d8a69e0a6..b504ab7b40a 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -860,13 +860,17 @@ PostprocessIndexStmt(Node *node, const char *queryString) * when CREATE INDEX CONCURRENTLY IF NOT EXISTS is run on an already * existing index. */ - bool skip_invalidation = false; + bool indexWasAlreadyValid = false; if (indexStmt->if_not_exists) { /* * Check the current validity state of the index. If it's already valid, * it means the index existed before this command and PostgreSQL skipped * creation. We should not invalidate it in this case. + * + * Note: SearchSysCache1 may return an invalid tuple if the index doesn't + * exist in the catalog, which would mean the index was just created. + * HeapTupleIsValid handles this case safely. */ HeapTuple indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexRelationId)); @@ -876,14 +880,14 @@ PostprocessIndexStmt(Node *node, const char *queryString) if (indexForm->indisvalid) { /* Index was already valid, so it existed before this command */ - skip_invalidation = true; + indexWasAlreadyValid = true; } ReleaseSysCache(indexTuple); } } /* mark index as invalid, in-place (cannot be rolled back) */ - if (!skip_invalidation) + if (!indexWasAlreadyValid) { index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); } From 163612409c6503da7bdb42446cefe2b42a22b77a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:34:21 +0000 Subject: [PATCH 7/8] Fix coding style: use snake_case and remove trailing whitespace Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- src/backend/distributed/commands/index.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index b504ab7b40a..9d6a4e77072 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -860,14 +860,14 @@ PostprocessIndexStmt(Node *node, const char *queryString) * when CREATE INDEX CONCURRENTLY IF NOT EXISTS is run on an already * existing index. */ - bool indexWasAlreadyValid = false; + bool index_was_already_valid = false; if (indexStmt->if_not_exists) { /* * Check the current validity state of the index. If it's already valid, * it means the index existed before this command and PostgreSQL skipped * creation. We should not invalidate it in this case. - * + * * Note: SearchSysCache1 may return an invalid tuple if the index doesn't * exist in the catalog, which would mean the index was just created. * HeapTupleIsValid handles this case safely. @@ -880,14 +880,14 @@ PostprocessIndexStmt(Node *node, const char *queryString) if (indexForm->indisvalid) { /* Index was already valid, so it existed before this command */ - indexWasAlreadyValid = true; + index_was_already_valid = true; } ReleaseSysCache(indexTuple); } } /* mark index as invalid, in-place (cannot be rolled back) */ - if (!indexWasAlreadyValid) + if (!index_was_already_valid) { index_set_state_flags(indexRelationId, INDEX_DROP_CLEAR_VALID); } From f40f872a695dbe6ee6834514c3f5fbcabf59035a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:35:01 +0000 Subject: [PATCH 8/8] Improve comment clarity and accuracy Co-authored-by: emelsimsek <13130350+emelsimsek@users.noreply.github.com> --- src/backend/distributed/commands/index.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/backend/distributed/commands/index.c b/src/backend/distributed/commands/index.c index 9d6a4e77072..682e18cef71 100644 --- a/src/backend/distributed/commands/index.c +++ b/src/backend/distributed/commands/index.c @@ -855,10 +855,8 @@ PostprocessIndexStmt(Node *node, const char *queryString) PushActiveSnapshot(GetTransactionSnapshot()); /* - * If IF NOT EXISTS was used and the index already existed (is valid), - * skip invalidating it. This prevents invalidating an existing index - * when CREATE INDEX CONCURRENTLY IF NOT EXISTS is run on an already - * existing index. + * If IF NOT EXISTS was used and the index already exists (is valid), + * skip invalidating it to prevent invalidating pre-existing indexes. */ bool index_was_already_valid = false; if (indexStmt->if_not_exists) @@ -868,9 +866,9 @@ PostprocessIndexStmt(Node *node, const char *queryString) * it means the index existed before this command and PostgreSQL skipped * creation. We should not invalidate it in this case. * - * Note: SearchSysCache1 may return an invalid tuple if the index doesn't - * exist in the catalog, which would mean the index was just created. - * HeapTupleIsValid handles this case safely. + * Note: SearchSysCache1 returns NULL if the index doesn't exist in the + * catalog yet, indicating it was just created. HeapTupleIsValid handles + * this case by returning false. */ HeapTuple indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexRelationId));