@@ -72,6 +72,13 @@ typedef struct RestCatalogTokenCacheEntry
7272} RestCatalogTokenCacheEntry ;
7373
7474static HTAB * RestCatalogTokenCache = NULL ;
75+ static MemoryContext RestTokenCacheCtx = NULL ;
76+
77+ /*
78+ * Tracks which server's request is in flight so the retry callback can
79+ * invalidate only the right token cache entry.
80+ */
81+ static const char * CurrentRetryServerName = NULL ;
7582
7683static char * GetRestCatalogAccessToken (RestCatalogConnectionInfo * conn , bool forceRefreshToken );
7784static void FetchRestCatalogAccessToken (RestCatalogConnectionInfo * conn , char * * accessToken , int * expiresIn );
@@ -329,11 +336,7 @@ GetRestCatalogConnectionFromServer(const char *serverName)
329336 ForeignServer * server = GetForeignServerByName (serverName , false);
330337 ForeignDataWrapper * fdw = GetForeignDataWrapper (server -> fdwid );
331338
332- if (strcmp (fdw -> fdwname , ICEBERG_CATALOG_FDW_NAME ) != 0 )
333- ereport (ERROR ,
334- (errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
335- errmsg ("server \"%s\" does not use the iceberg_catalog foreign data wrapper" ,
336- serverName )));
339+ Assert (strcmp (fdw -> fdwname , ICEBERG_CATALOG_FDW_NAME ) == 0 );
337340
338341 RestCatalogConnectionInfo * conn = palloc0 (sizeof (RestCatalogConnectionInfo ));
339342
@@ -462,7 +465,8 @@ StartStageRestCatalogIcebergTableCreate(Oid relationId)
462465 headers = lappend (headers , vendedCreds );
463466 }
464467
465- HttpResult httpResult = SendRequestToRestCatalog (HTTP_POST , postUrl , body -> data , headers );
468+ HttpResult httpResult = SendRequestToRestCatalog (HTTP_POST , postUrl , body -> data ,
469+ headers , conn -> serverName );
466470
467471 if (httpResult .status != 200 )
468472 {
@@ -596,7 +600,9 @@ RegisterNamespaceToRestCatalog(RestCatalogConnectionInfo * conn, const char *cat
596600 psprintf (REST_CATALOG_NAMESPACE_NAME ,
597601 conn -> host , URLEncodePath (catalogName ),
598602 URLEncodePath (namespaceName ));
599- HttpResult httpResult = SendRequestToRestCatalog (HTTP_GET , getUrl , NULL , GetHeadersWithAuth (conn ));
603+ HttpResult httpResult = SendRequestToRestCatalog (HTTP_GET , getUrl , NULL ,
604+ GetHeadersWithAuth (conn ),
605+ conn -> serverName );
600606
601607 switch (httpResult .status )
602608 {
@@ -686,8 +692,9 @@ ErrorIfRestNamespaceDoesNotExist(RestCatalogConnectionInfo * conn, const char *c
686692 psprintf (REST_CATALOG_NAMESPACE_NAME ,
687693 conn -> host , URLEncodePath (catalogName ),
688694 URLEncodePath (namespaceName ));
689- HttpResult httpResult = SendRequestToRestCatalog (HTTP_GET , getUrl , NULL , GetHeadersWithAuth (conn ));
690-
695+ HttpResult httpResult = SendRequestToRestCatalog (HTTP_GET , getUrl , NULL ,
696+ GetHeadersWithAuth (conn ),
697+ conn -> serverName );
691698
692699 /* namespace not found */
693700 if (httpResult .status == 404 )
@@ -736,7 +743,8 @@ GetMetadataLocationFromRestCatalog(RestCatalogConnectionInfo * conn, const char
736743 conn -> host , URLEncodePath (restCatalogName ), URLEncodePath (namespaceName ), URLEncodePath (relationName ));
737744
738745 List * headers = GetHeadersWithAuth (conn );
739- HttpResult hr = SendRequestToRestCatalog (HTTP_GET , getUrl , NULL , headers );
746+ HttpResult hr = SendRequestToRestCatalog (HTTP_GET , getUrl , NULL , headers ,
747+ conn -> serverName );
740748
741749 if (hr .status != 200 )
742750 {
@@ -784,7 +792,9 @@ CreateNamespaceOnRestCatalog(RestCatalogConnectionInfo * conn, const char *catal
784792 psprintf (REST_CATALOG_NAMESPACE , conn -> host ,
785793 URLEncodePath (catalogName ));
786794
787- HttpResult httpResult = SendRequestToRestCatalog (HTTP_POST , postUrl , body .data , PostHeadersWithAuth (conn ));
795+ HttpResult httpResult = SendRequestToRestCatalog (HTTP_POST , postUrl , body .data ,
796+ PostHeadersWithAuth (conn ),
797+ conn -> serverName );
788798
789799 if (httpResult .status != 200 )
790800 {
@@ -889,12 +899,16 @@ InitTokenCacheIfNeeded(void)
889899 if (RestCatalogTokenCache != NULL )
890900 return ;
891901
902+ RestTokenCacheCtx = AllocSetContextCreate (TopMemoryContext ,
903+ "RestTokenCacheCtx" ,
904+ ALLOCSET_DEFAULT_SIZES );
905+
892906 HASHCTL ctl ;
893907
894908 memset (& ctl , 0 , sizeof (ctl ));
895909 ctl .keysize = TOKEN_CACHE_KEY_LEN ;
896910 ctl .entrysize = sizeof (RestCatalogTokenCacheEntry );
897- ctl .hcxt = TopMemoryContext ;
911+ ctl .hcxt = RestTokenCacheCtx ;
898912
899913 RestCatalogTokenCache = hash_create ("REST Catalog Token Cache" ,
900914 8 , & ctl ,
@@ -943,7 +957,7 @@ GetRestCatalogAccessToken(RestCatalogConnectionInfo * conn, bool forceRefreshTok
943957
944958 FetchRestCatalogAccessToken (conn , & accessToken , & expiresIn );
945959
946- entry -> accessToken = MemoryContextStrdup (TopMemoryContext , accessToken );
960+ entry -> accessToken = MemoryContextStrdup (RestTokenCacheCtx , accessToken );
947961 entry -> accessTokenExpiry = now + (int64_t ) expiresIn * 1000000 ; /* expiresIn is in
948962 * seconds */
949963 }
@@ -961,9 +975,15 @@ static void
961975FetchRestCatalogAccessToken (RestCatalogConnectionInfo * conn , char * * accessToken , int * expiresIn )
962976{
963977 if (!conn -> host || !* conn -> host )
964- ereport (ERROR , (errmsg ("REST catalog host is not configured" )));
978+ ereport (ERROR ,
979+ (errmsg ("REST catalog host is not configured" ),
980+ errhint ("Set the \"rest_endpoint\" option on the server "
981+ "or the pg_lake_iceberg.rest_catalog_host GUC." )));
965982 if (!conn -> clientSecret || !* conn -> clientSecret )
966- ereport (ERROR , (errmsg ("REST catalog client_secret is not configured" )));
983+ ereport (ERROR ,
984+ (errmsg ("REST catalog client_secret is not configured" ),
985+ errhint ("Set the \"client_secret\" option on the server "
986+ "or the pg_lake_iceberg.rest_catalog_client_secret GUC." )));
967987
968988 char * accessTokenUrl = conn -> oauthHostPath ;
969989
@@ -991,7 +1011,10 @@ FetchRestCatalogAccessToken(RestCatalogConnectionInfo * conn, char **accessToken
9911011 else
9921012 {
9931013 if (!conn -> clientId || !* conn -> clientId )
994- ereport (ERROR , (errmsg ("REST catalog client_id is not configured" )));
1014+ ereport (ERROR ,
1015+ (errmsg ("REST catalog client_id is not configured" ),
1016+ errhint ("Set the \"client_id\" option on the server "
1017+ "or the pg_lake_iceberg.rest_catalog_client_id GUC." )));
9951018
9961019 /* Build Authorization: Basic <base64(clientId:clientSecret)> */
9971020 char * encodedAuth = EncodeBasicAuth (conn -> clientId , conn -> clientSecret );
@@ -1003,7 +1026,9 @@ FetchRestCatalogAccessToken(RestCatalogConnectionInfo * conn, char **accessToken
10031026 headers = lappend (headers , "Content-Type: application/x-www-form-urlencoded" );
10041027
10051028 /* POST */
1006- HttpResult httpResponse = SendRequestToRestCatalog (HTTP_POST , accessTokenUrl , body .data , headers );
1029+ HttpResult httpResponse = SendRequestToRestCatalog (HTTP_POST , accessTokenUrl ,
1030+ body .data , headers ,
1031+ conn -> serverName );
10071032
10081033 if (httpResponse .status != 200 )
10091034 ereport (ERROR ,
@@ -1458,14 +1483,24 @@ GetRemoveSnapshotCatalogRequest(List *removedSnapshotIds, Oid relationId)
14581483/*
14591484 * SendRequestToRestCatalog sends an HTTP request to the rest catalog
14601485 * with retry logic for retriable errors, attempting up to MAX_HTTP_RETRY_FOR_REST_CATALOG
1461- * times.
1486+ * times. The serverName is used by the retry callback to invalidate only the
1487+ * matching token cache entry on a 419 (token expired) response.
14621488 */
14631489HttpResult
1464- SendRequestToRestCatalog (HttpMethod method , const char * url , const char * body , List * headers )
1490+ SendRequestToRestCatalog (HttpMethod method , const char * url , const char * body ,
1491+ List * headers , const char * serverName )
14651492{
14661493 const int MAX_HTTP_RETRY_FOR_REST_CATALOG = 3 ;
14671494
1468- return SendHttpRequestWithRetry (method , url , body , headers , ShouldRetryRequestToRestCatalog , MAX_HTTP_RETRY_FOR_REST_CATALOG );
1495+ CurrentRetryServerName = serverName ;
1496+
1497+ HttpResult result = SendHttpRequestWithRetry (method , url , body , headers ,
1498+ ShouldRetryRequestToRestCatalog ,
1499+ MAX_HTTP_RETRY_FOR_REST_CATALOG );
1500+
1501+ CurrentRetryServerName = NULL ;
1502+
1503+ return result ;
14691504}
14701505
14711506
@@ -1508,26 +1543,15 @@ ShouldRetryRequestToRestCatalog(long status, int maxRetry, int retryNo)
15081543 return true;
15091544 }
15101545
1511- /* token expired, retry after refreshing token */
1546+ /* token expired: invalidate only the affected server's cached token */
15121547 else if (status == TOKEN_EXPIRED_STATUS )
15131548 {
1514- /*
1515- * Invalidate all cached tokens so that the next request will fetch a
1516- * fresh one. We clear the entire cache because the retry callback
1517- * does not have access to a specific connection's info. This is safe
1518- * because token expiry is rare and other connections will simply
1519- * re-authenticate on their next request.
1520- */
1521- if (RestCatalogTokenCache != NULL )
1549+ if (RestCatalogTokenCache != NULL && CurrentRetryServerName != NULL )
15221550 {
1523- HASH_SEQ_STATUS seq ;
1524- RestCatalogTokenCacheEntry * entry ;
1551+ char cacheKey [TOKEN_CACHE_KEY_LEN ];
15251552
1526- hash_seq_init (& seq , RestCatalogTokenCache );
1527- while ((entry = hash_seq_search (& seq )) != NULL )
1528- {
1529- entry -> accessTokenExpiry = 0 ;
1530- }
1553+ strlcpy (cacheKey , CurrentRetryServerName , TOKEN_CACHE_KEY_LEN );
1554+ hash_search (RestCatalogTokenCache , cacheKey , HASH_REMOVE , NULL );
15311555 }
15321556 return true;
15331557 }
0 commit comments