diff --git a/src/backend/utils/adt/ddlutils.c b/src/backend/utils/adt/ddlutils.c
index d83cda33..b57e7d27 100644
--- a/src/backend/utils/adt/ddlutils.c
+++ b/src/backend/utils/adt/ddlutils.c
@@ -35,6 +35,7 @@
 #include "utils/acl.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/datetime.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -984,9 +985,13 @@ pg_get_database_ddl_internal(Oid dbid, bool pretty,
 	/* TABLESPACE */
 	if (!no_tablespace && OidIsValid(dbform->dattablespace))
 	{
-		char	   *spcname = get_tablespace_name(dbform->dattablespace);
+		char	   *spcname;
 
-		if (pg_strcasecmp(spcname, "pg_default") != 0)
+		INJECTION_POINT("pg_get_database_ddl-before-get_tablespace_name", NULL);
+		spcname = get_tablespace_name(dbform->dattablespace);
+
+		if (spcname != NULL &&
+			pg_strcasecmp(spcname, "pg_default") != 0)
 			append_ddl_option(&buf, pretty, 4, "TABLESPACE = %s",
 							  quote_identifier(spcname));
 	}
diff --git a/src/test/modules/test_misc/t/012_ddlutils_tablespace_race.pl b/src/test/modules/test_misc/t/012_ddlutils_tablespace_race.pl
new file mode 100644
index 00000000..76d31afb
--- /dev/null
+++ b/src/test/modules/test_misc/t/012_ddlutils_tablespace_race.pl
@@ -0,0 +1,111 @@
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test for race condition in pg_get_database_ddl() when a tablespace
+# is dropped concurrently.
+#
+# pg_get_database_ddl() reads the database's dattablespace OID from the
+# syscache, then later calls get_tablespace_name() which does a fresh
+# catalog scan.  If the tablespace is dropped between these two operations,
+# get_tablespace_name() returns NULL and the unpatched code passes NULL
+# to pg_strcasecmp(), causing a SIGSEGV.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+plan skip_all => 'Injection points not supported by this build'
+  unless $ENV{enable_injection_points} eq 'yes';
+
+# Node initialization
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init();
+$node->append_conf('postgresql.conf',
+	"shared_preload_libraries = 'injection_points'");
+$node->start();
+
+# Check if the extension injection_points is available
+plan skip_all => 'Extension injection_points not installed'
+  unless $node->check_extension('injection_points');
+
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+# Create tablespace and database using it
+$node->safe_psql('postgres', q[
+SET allow_in_place_tablespaces = on;
+CREATE TABLESPACE race_ts LOCATION '';
+]);
+$node->safe_psql('postgres',
+	"CREATE DATABASE race_db TABLESPACE race_ts;");
+
+# Verify setup
+my $result = $node->safe_psql('postgres',
+	"SELECT spcname FROM pg_database d JOIN pg_tablespace t "
+	. "ON d.dattablespace = t.oid WHERE datname = 'race_db';");
+is($result, 'race_ts', 'race_db is on race_ts tablespace');
+
+############################################################################
+note('Test: pg_get_database_ddl with concurrent tablespace drop');
+
+# Attach injection point that pauses before get_tablespace_name()
+$node->safe_psql('postgres',
+	"SELECT injection_points_attach("
+	. "'pg_get_database_ddl-before-get_tablespace_name', 'wait');");
+
+# Session A: call pg_get_database_ddl - will block at injection point
+my $session_a = $node->background_psql('postgres');
+
+$session_a->query_until(
+	qr//, q[
+SELECT pg_get_database_ddl('race_db'::regdatabase);
+\g
+]);
+
+# Wait for Session A to reach the injection point
+$node->wait_for_event('client backend',
+	'pg_get_database_ddl-before-get_tablespace_name');
+
+note('Session A is paused before get_tablespace_name()');
+
+# Session B: move database off the tablespace and drop it
+$node->safe_psql('postgres',
+	"ALTER DATABASE race_db SET TABLESPACE pg_default;");
+$node->safe_psql('postgres', "DROP TABLESPACE race_ts;");
+
+# Verify the tablespace is gone
+$result = $node->safe_psql('postgres',
+	"SELECT count(*) FROM pg_tablespace WHERE spcname = 'race_ts';");
+is($result, '0', 'tablespace race_ts has been dropped');
+
+note('Waking up Session A - get_tablespace_name() will return NULL');
+
+# Wake up Session A - it will now call get_tablespace_name() with a
+# stale OID that no longer exists.  Without the NULL check fix, this
+# crashes with SIGSEGV in pg_strcasecmp(NULL, "pg_default").
+$node->safe_psql('postgres',
+	"SELECT injection_points_wakeup("
+	. "'pg_get_database_ddl-before-get_tablespace_name');");
+
+# Collect result from Session A
+my $output = $session_a->query_safe(q[\g]);
+$session_a->quit();
+
+# The DDL should succeed and NOT include TABLESPACE (since the database
+# is now on pg_default)
+like($output, qr/CREATE DATABASE race_db/,
+	'pg_get_database_ddl completed without crash');
+unlike($output, qr/TABLESPACE/,
+	'DDL output does not include dropped tablespace');
+
+# Detach injection point
+$node->safe_psql('postgres',
+	"SELECT injection_points_detach("
+	. "'pg_get_database_ddl-before-get_tablespace_name');");
+
+# Clean up
+$node->safe_psql('postgres', "DROP DATABASE race_db;");
+
+done_testing();
